{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "**Chapter 11 – Training Deep Neural Networks**"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "_This notebook contains all the sample code and solutions to the exercises in chapter 11._"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "<table align=\"left\">\n",
    "  <td>\n",
    "    <a target=\"_blank\" href=\"https://colab.research.google.com/github/ageron/handson-ml2/blob/master/11_training_deep_neural_networks.ipynb\"><img src=\"https://www.tensorflow.org/images/colab_logo_32px.png\" />Run in Google Colab</a>\n",
    "  </td>\n",
    "</table>"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# Setup"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "First, let's import a few common modules, ensure MatplotLib plots figures inline and prepare a function to save the figures. We also check that Python 3.5 or later is installed (although Python 2.x may work, it is deprecated so we strongly recommend you use Python 3 instead), as well as Scikit-Learn ≥0.20 and TensorFlow ≥2.0."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 1,
   "metadata": {},
   "outputs": [],
   "source": [
    "# Python ≥3.5 is required\n",
    "import sys\n",
    "assert sys.version_info >= (3, 5)\n",
    "\n",
    "# Scikit-Learn ≥0.20 is required\n",
    "import sklearn\n",
    "assert sklearn.__version__ >= \"0.20\"\n",
    "\n",
    "try:\n",
    "    # %tensorflow_version only exists in Colab.\n",
    "    %tensorflow_version 2.x\n",
    "except Exception:\n",
    "    pass\n",
    "\n",
    "# TensorFlow ≥2.0 is required\n",
    "import tensorflow as tf\n",
    "from tensorflow import keras\n",
    "assert tf.__version__ >= \"2.0\"\n",
    "\n",
    "# Common imports\n",
    "import numpy as np\n",
    "import os\n",
    "\n",
    "# to make this notebook's output stable across runs\n",
    "np.random.seed(42)\n",
    "\n",
    "# To plot pretty figures\n",
    "%matplotlib inline\n",
    "import matplotlib as mpl\n",
    "import matplotlib.pyplot as plt\n",
    "mpl.rc('axes', labelsize=14)\n",
    "mpl.rc('xtick', labelsize=12)\n",
    "mpl.rc('ytick', labelsize=12)\n",
    "\n",
    "# Where to save the figures\n",
    "PROJECT_ROOT_DIR = \".\"\n",
    "CHAPTER_ID = \"deep\"\n",
    "IMAGES_PATH = os.path.join(PROJECT_ROOT_DIR, \"images\", CHAPTER_ID)\n",
    "os.makedirs(IMAGES_PATH, exist_ok=True)\n",
    "\n",
    "def save_fig(fig_id, tight_layout=True, fig_extension=\"png\", resolution=300):\n",
    "    path = os.path.join(IMAGES_PATH, fig_id + \".\" + fig_extension)\n",
    "    print(\"Saving figure\", fig_id)\n",
    "    if tight_layout:\n",
    "        plt.tight_layout()\n",
    "    plt.savefig(path, format=fig_extension, dpi=resolution)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# Vanishing/Exploding Gradients Problem"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 2,
   "metadata": {},
   "outputs": [],
   "source": [
    "def logit(z):\n",
    "    return 1 / (1 + np.exp(-z))"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 3,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Saving figure sigmoid_saturation_plot\n"
     ]
    },
    {
     "data": {
      "image/png": "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\n",
      "text/plain": [
       "<Figure size 432x288 with 1 Axes>"
      ]
     },
     "metadata": {
      "needs_background": "light"
     },
     "output_type": "display_data"
    }
   ],
   "source": [
    "z = np.linspace(-5, 5, 200)\n",
    "\n",
    "plt.plot([-5, 5], [0, 0], 'k-')\n",
    "plt.plot([-5, 5], [1, 1], 'k--')\n",
    "plt.plot([0, 0], [-0.2, 1.2], 'k-')\n",
    "plt.plot([-5, 5], [-3/4, 7/4], 'g--')\n",
    "plt.plot(z, logit(z), \"b-\", linewidth=2)\n",
    "props = dict(facecolor='black', shrink=0.1)\n",
    "plt.annotate('Saturating', xytext=(3.5, 0.7), xy=(5, 1), arrowprops=props, fontsize=14, ha=\"center\")\n",
    "plt.annotate('Saturating', xytext=(-3.5, 0.3), xy=(-5, 0), arrowprops=props, fontsize=14, ha=\"center\")\n",
    "plt.annotate('Linear', xytext=(2, 0.2), xy=(0, 0.5), arrowprops=props, fontsize=14, ha=\"center\")\n",
    "plt.grid(True)\n",
    "plt.title(\"Sigmoid activation function\", fontsize=14)\n",
    "plt.axis([-5, 5, -0.2, 1.2])\n",
    "\n",
    "save_fig(\"sigmoid_saturation_plot\")\n",
    "plt.show()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Xavier and He Initialization"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 4,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "['Constant',\n",
       " 'GlorotNormal',\n",
       " 'GlorotUniform',\n",
       " 'Identity',\n",
       " 'Initializer',\n",
       " 'Ones',\n",
       " 'Orthogonal',\n",
       " 'RandomNormal',\n",
       " 'RandomUniform',\n",
       " 'TruncatedNormal',\n",
       " 'VarianceScaling',\n",
       " 'Zeros',\n",
       " 'constant',\n",
       " 'deserialize',\n",
       " 'get',\n",
       " 'glorot_normal',\n",
       " 'glorot_uniform',\n",
       " 'he_normal',\n",
       " 'he_uniform',\n",
       " 'identity',\n",
       " 'lecun_normal',\n",
       " 'lecun_uniform',\n",
       " 'ones',\n",
       " 'orthogonal',\n",
       " 'serialize',\n",
       " 'zeros']"
      ]
     },
     "execution_count": 4,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "[name for name in dir(keras.initializers) if not name.startswith(\"_\")]"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 5,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "<tensorflow.python.keras.layers.core.Dense at 0x1110c82e8>"
      ]
     },
     "execution_count": 5,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "keras.layers.Dense(10, activation=\"relu\", kernel_initializer=\"he_normal\")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 6,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "<tensorflow.python.keras.layers.core.Dense at 0x1110adeb8>"
      ]
     },
     "execution_count": 6,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "init = keras.initializers.VarianceScaling(scale=2., mode='fan_avg',\n",
    "                                          distribution='uniform')\n",
    "keras.layers.Dense(10, activation=\"relu\", kernel_initializer=init)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Nonsaturating Activation Functions"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Leaky ReLU"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 7,
   "metadata": {},
   "outputs": [],
   "source": [
    "def leaky_relu(z, alpha=0.01):\n",
    "    return np.maximum(alpha*z, z)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 8,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Saving figure leaky_relu_plot\n"
     ]
    },
    {
     "data": {
      "image/png": "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\n",
      "text/plain": [
       "<Figure size 432x288 with 1 Axes>"
      ]
     },
     "metadata": {
      "needs_background": "light"
     },
     "output_type": "display_data"
    }
   ],
   "source": [
    "plt.plot(z, leaky_relu(z, 0.05), \"b-\", linewidth=2)\n",
    "plt.plot([-5, 5], [0, 0], 'k-')\n",
    "plt.plot([0, 0], [-0.5, 4.2], 'k-')\n",
    "plt.grid(True)\n",
    "props = dict(facecolor='black', shrink=0.1)\n",
    "plt.annotate('Leak', xytext=(-3.5, 0.5), xy=(-5, -0.2), arrowprops=props, fontsize=14, ha=\"center\")\n",
    "plt.title(\"Leaky ReLU activation function\", fontsize=14)\n",
    "plt.axis([-5, 5, -0.5, 4.2])\n",
    "\n",
    "save_fig(\"leaky_relu_plot\")\n",
    "plt.show()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 9,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "['deserialize',\n",
       " 'elu',\n",
       " 'exponential',\n",
       " 'get',\n",
       " 'hard_sigmoid',\n",
       " 'linear',\n",
       " 'relu',\n",
       " 'selu',\n",
       " 'serialize',\n",
       " 'sigmoid',\n",
       " 'softmax',\n",
       " 'softplus',\n",
       " 'softsign',\n",
       " 'tanh']"
      ]
     },
     "execution_count": 9,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "[m for m in dir(keras.activations) if not m.startswith(\"_\")]"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 10,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "['LeakyReLU', 'PReLU', 'ReLU', 'ThresholdedReLU']"
      ]
     },
     "execution_count": 10,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "[m for m in dir(keras.layers) if \"relu\" in m.lower()]"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Let's train a neural network on Fashion MNIST using the Leaky ReLU:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 11,
   "metadata": {},
   "outputs": [],
   "source": [
    "(X_train_full, y_train_full), (X_test, y_test) = keras.datasets.fashion_mnist.load_data()\n",
    "X_train_full = X_train_full / 255.0\n",
    "X_test = X_test / 255.0\n",
    "X_valid, X_train = X_train_full[:5000], X_train_full[5000:]\n",
    "y_valid, y_train = y_train_full[:5000], y_train_full[5000:]"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 12,
   "metadata": {},
   "outputs": [],
   "source": [
    "tf.random.set_seed(42)\n",
    "np.random.seed(42)\n",
    "\n",
    "model = keras.models.Sequential([\n",
    "    keras.layers.Flatten(input_shape=[28, 28]),\n",
    "    keras.layers.Dense(300, kernel_initializer=\"he_normal\"),\n",
    "    keras.layers.LeakyReLU(),\n",
    "    keras.layers.Dense(100, kernel_initializer=\"he_normal\"),\n",
    "    keras.layers.LeakyReLU(),\n",
    "    keras.layers.Dense(10, activation=\"softmax\")\n",
    "])"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 13,
   "metadata": {},
   "outputs": [],
   "source": [
    "model.compile(loss=\"sparse_categorical_crossentropy\",\n",
    "              optimizer=keras.optimizers.SGD(lr=1e-3),\n",
    "              metrics=[\"accuracy\"])"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 14,
   "metadata": {
    "scrolled": true
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Train on 55000 samples, validate on 5000 samples\n",
      "Epoch 1/10\n",
      "55000/55000 [==============================] - 3s 50us/sample - loss: 1.2806 - accuracy: 0.6250 - val_loss: 0.8883 - val_accuracy: 0.7152\n",
      "Epoch 2/10\n",
      "55000/55000 [==============================] - 2s 40us/sample - loss: 0.7954 - accuracy: 0.7373 - val_loss: 0.7135 - val_accuracy: 0.7648\n",
      "Epoch 3/10\n",
      "55000/55000 [==============================] - 2s 42us/sample - loss: 0.6816 - accuracy: 0.7727 - val_loss: 0.6356 - val_accuracy: 0.7882\n",
      "Epoch 4/10\n",
      "55000/55000 [==============================] - 2s 42us/sample - loss: 0.6215 - accuracy: 0.7935 - val_loss: 0.5922 - val_accuracy: 0.8012\n",
      "Epoch 5/10\n",
      "55000/55000 [==============================] - 2s 42us/sample - loss: 0.5830 - accuracy: 0.8081 - val_loss: 0.5596 - val_accuracy: 0.8172\n",
      "Epoch 6/10\n",
      "55000/55000 [==============================] - 2s 42us/sample - loss: 0.5553 - accuracy: 0.8155 - val_loss: 0.5338 - val_accuracy: 0.8240\n",
      "Epoch 7/10\n",
      "55000/55000 [==============================] - 2s 40us/sample - loss: 0.5340 - accuracy: 0.8221 - val_loss: 0.5157 - val_accuracy: 0.8310\n",
      "Epoch 8/10\n",
      "55000/55000 [==============================] - 2s 41us/sample - loss: 0.5172 - accuracy: 0.8265 - val_loss: 0.5035 - val_accuracy: 0.8336\n",
      "Epoch 9/10\n",
      "55000/55000 [==============================] - 2s 42us/sample - loss: 0.5036 - accuracy: 0.8299 - val_loss: 0.4950 - val_accuracy: 0.8354\n",
      "Epoch 10/10\n",
      "55000/55000 [==============================] - 2s 42us/sample - loss: 0.4922 - accuracy: 0.8324 - val_loss: 0.4797 - val_accuracy: 0.8430\n"
     ]
    }
   ],
   "source": [
    "history = model.fit(X_train, y_train, epochs=10,\n",
    "                    validation_data=(X_valid, y_valid))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Now let's try PReLU:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 15,
   "metadata": {},
   "outputs": [],
   "source": [
    "tf.random.set_seed(42)\n",
    "np.random.seed(42)\n",
    "\n",
    "model = keras.models.Sequential([\n",
    "    keras.layers.Flatten(input_shape=[28, 28]),\n",
    "    keras.layers.Dense(300, kernel_initializer=\"he_normal\"),\n",
    "    keras.layers.PReLU(),\n",
    "    keras.layers.Dense(100, kernel_initializer=\"he_normal\"),\n",
    "    keras.layers.PReLU(),\n",
    "    keras.layers.Dense(10, activation=\"softmax\")\n",
    "])"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 16,
   "metadata": {},
   "outputs": [],
   "source": [
    "model.compile(loss=\"sparse_categorical_crossentropy\",\n",
    "              optimizer=keras.optimizers.SGD(lr=1e-3),\n",
    "              metrics=[\"accuracy\"])"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 17,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Train on 55000 samples, validate on 5000 samples\n",
      "Epoch 1/10\n",
      "55000/55000 [==============================] - 3s 61us/sample - loss: 1.3460 - accuracy: 0.6233 - val_loss: 0.9251 - val_accuracy: 0.7208\n",
      "Epoch 2/10\n",
      "55000/55000 [==============================] - 3s 56us/sample - loss: 0.8208 - accuracy: 0.7359 - val_loss: 0.7318 - val_accuracy: 0.7626\n",
      "Epoch 3/10\n",
      "55000/55000 [==============================] - 3s 55us/sample - loss: 0.6974 - accuracy: 0.7695 - val_loss: 0.6500 - val_accuracy: 0.7886\n",
      "Epoch 4/10\n",
      "55000/55000 [==============================] - 3s 55us/sample - loss: 0.6338 - accuracy: 0.7904 - val_loss: 0.6000 - val_accuracy: 0.8070\n",
      "Epoch 5/10\n",
      "55000/55000 [==============================] - 3s 57us/sample - loss: 0.5920 - accuracy: 0.8045 - val_loss: 0.5662 - val_accuracy: 0.8172\n",
      "Epoch 6/10\n",
      "55000/55000 [==============================] - 3s 55us/sample - loss: 0.5620 - accuracy: 0.8138 - val_loss: 0.5416 - val_accuracy: 0.8230\n",
      "Epoch 7/10\n",
      "55000/55000 [==============================] - 3s 55us/sample - loss: 0.5393 - accuracy: 0.8203 - val_loss: 0.5218 - val_accuracy: 0.8302\n",
      "Epoch 8/10\n",
      "55000/55000 [==============================] - 3s 57us/sample - loss: 0.5216 - accuracy: 0.8248 - val_loss: 0.5051 - val_accuracy: 0.8340\n",
      "Epoch 9/10\n",
      "55000/55000 [==============================] - 3s 59us/sample - loss: 0.5069 - accuracy: 0.8289 - val_loss: 0.4923 - val_accuracy: 0.8384\n",
      "Epoch 10/10\n",
      "55000/55000 [==============================] - 3s 62us/sample - loss: 0.4948 - accuracy: 0.8322 - val_loss: 0.4847 - val_accuracy: 0.8372\n"
     ]
    }
   ],
   "source": [
    "history = model.fit(X_train, y_train, epochs=10,\n",
    "                    validation_data=(X_valid, y_valid))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### ELU"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 18,
   "metadata": {},
   "outputs": [],
   "source": [
    "def elu(z, alpha=1):\n",
    "    return np.where(z < 0, alpha * (np.exp(z) - 1), z)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 19,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Saving figure elu_plot\n"
     ]
    },
    {
     "data": {
      "image/png": "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\n",
      "text/plain": [
       "<Figure size 432x288 with 1 Axes>"
      ]
     },
     "metadata": {
      "needs_background": "light"
     },
     "output_type": "display_data"
    }
   ],
   "source": [
    "plt.plot(z, elu(z), \"b-\", linewidth=2)\n",
    "plt.plot([-5, 5], [0, 0], 'k-')\n",
    "plt.plot([-5, 5], [-1, -1], 'k--')\n",
    "plt.plot([0, 0], [-2.2, 3.2], 'k-')\n",
    "plt.grid(True)\n",
    "plt.title(r\"ELU activation function ($\\alpha=1$)\", fontsize=14)\n",
    "plt.axis([-5, 5, -2.2, 3.2])\n",
    "\n",
    "save_fig(\"elu_plot\")\n",
    "plt.show()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Implementing ELU in TensorFlow is trivial, just specify the activation function when building each layer:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 20,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "<tensorflow.python.keras.layers.core.Dense at 0x10dca50f0>"
      ]
     },
     "execution_count": 20,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "keras.layers.Dense(10, activation=\"elu\")"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### SELU"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "This activation function was proposed in this [great paper](https://arxiv.org/pdf/1706.02515.pdf) by Günter Klambauer, Thomas Unterthiner and Andreas Mayr, published in June 2017. During training, a neural network composed exclusively of a stack of dense layers using the SELU activation function and LeCun initialization will self-normalize: the output of each layer will tend to preserve the same mean and variance during training, which solves the vanishing/exploding gradients problem. As a result, this activation function outperforms the other activation functions very significantly for such neural nets, so you should really try it out. Unfortunately, the self-normalizing property of the SELU activation function is easily broken: you cannot use ℓ<sub>1</sub> or ℓ<sub>2</sub> regularization, regular dropout, max-norm, skip connections or other non-sequential topologies (so recurrent neural networks won't self-normalize). However, in practice it works quite well with sequential CNNs. If you break self-normalization, SELU will not necessarily outperform other activation functions."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 21,
   "metadata": {},
   "outputs": [],
   "source": [
    "from scipy.special import erfc\n",
    "\n",
    "# alpha and scale to self normalize with mean 0 and standard deviation 1\n",
    "# (see equation 14 in the paper):\n",
    "alpha_0_1 = -np.sqrt(2 / np.pi) / (erfc(1/np.sqrt(2)) * np.exp(1/2) - 1)\n",
    "scale_0_1 = (1 - erfc(1 / np.sqrt(2)) * np.sqrt(np.e)) * np.sqrt(2 * np.pi) * (2 * erfc(np.sqrt(2))*np.e**2 + np.pi*erfc(1/np.sqrt(2))**2*np.e - 2*(2+np.pi)*erfc(1/np.sqrt(2))*np.sqrt(np.e)+np.pi+2)**(-1/2)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 22,
   "metadata": {},
   "outputs": [],
   "source": [
    "def selu(z, scale=scale_0_1, alpha=alpha_0_1):\n",
    "    return scale * elu(z, alpha)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 23,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Saving figure selu_plot\n"
     ]
    },
    {
     "data": {
      "image/png": "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\n",
      "text/plain": [
       "<Figure size 432x288 with 1 Axes>"
      ]
     },
     "metadata": {
      "needs_background": "light"
     },
     "output_type": "display_data"
    }
   ],
   "source": [
    "plt.plot(z, selu(z), \"b-\", linewidth=2)\n",
    "plt.plot([-5, 5], [0, 0], 'k-')\n",
    "plt.plot([-5, 5], [-1.758, -1.758], 'k--')\n",
    "plt.plot([0, 0], [-2.2, 3.2], 'k-')\n",
    "plt.grid(True)\n",
    "plt.title(\"SELU activation function\", fontsize=14)\n",
    "plt.axis([-5, 5, -2.2, 3.2])\n",
    "\n",
    "save_fig(\"selu_plot\")\n",
    "plt.show()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "By default, the SELU hyperparameters (`scale` and `alpha`) are tuned in such a way that the mean output of each neuron remains close to 0, and the standard deviation remains close to 1 (assuming the inputs are standardized with mean 0 and standard deviation 1 too). Using this activation function, even a 1,000 layer deep neural network preserves roughly mean 0 and standard deviation 1 across all layers, avoiding the exploding/vanishing gradients problem:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 24,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Layer 0: mean -0.00, std deviation 1.00\n",
      "Layer 100: mean 0.02, std deviation 0.96\n",
      "Layer 200: mean 0.01, std deviation 0.90\n",
      "Layer 300: mean -0.02, std deviation 0.92\n",
      "Layer 400: mean 0.05, std deviation 0.89\n",
      "Layer 500: mean 0.01, std deviation 0.93\n",
      "Layer 600: mean 0.02, std deviation 0.92\n",
      "Layer 700: mean -0.02, std deviation 0.90\n",
      "Layer 800: mean 0.05, std deviation 0.83\n",
      "Layer 900: mean 0.02, std deviation 1.00\n"
     ]
    }
   ],
   "source": [
    "np.random.seed(42)\n",
    "Z = np.random.normal(size=(500, 100)) # standardized inputs\n",
    "for layer in range(1000):\n",
    "    W = np.random.normal(size=(100, 100), scale=np.sqrt(1 / 100)) # LeCun initialization\n",
    "    Z = selu(np.dot(Z, W))\n",
    "    means = np.mean(Z, axis=0).mean()\n",
    "    stds = np.std(Z, axis=0).mean()\n",
    "    if layer % 100 == 0:\n",
    "        print(\"Layer {}: mean {:.2f}, std deviation {:.2f}\".format(layer, means, stds))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Using SELU is easy:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 25,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "<tensorflow.python.keras.layers.core.Dense at 0x158a45630>"
      ]
     },
     "execution_count": 25,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "keras.layers.Dense(10, activation=\"selu\",\n",
    "                   kernel_initializer=\"lecun_normal\")"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Let's create a neural net for Fashion MNIST with 100 hidden layers, using the SELU activation function:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 26,
   "metadata": {},
   "outputs": [],
   "source": [
    "np.random.seed(42)\n",
    "tf.random.set_seed(42)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 27,
   "metadata": {},
   "outputs": [],
   "source": [
    "model = keras.models.Sequential()\n",
    "model.add(keras.layers.Flatten(input_shape=[28, 28]))\n",
    "model.add(keras.layers.Dense(300, activation=\"selu\",\n",
    "                             kernel_initializer=\"lecun_normal\"))\n",
    "for layer in range(99):\n",
    "    model.add(keras.layers.Dense(100, activation=\"selu\",\n",
    "                                 kernel_initializer=\"lecun_normal\"))\n",
    "model.add(keras.layers.Dense(10, activation=\"softmax\"))"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 28,
   "metadata": {},
   "outputs": [],
   "source": [
    "model.compile(loss=\"sparse_categorical_crossentropy\",\n",
    "              optimizer=keras.optimizers.SGD(lr=1e-3),\n",
    "              metrics=[\"accuracy\"])"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Now let's train it. Do not forget to scale the inputs to mean 0 and standard deviation 1:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 29,
   "metadata": {},
   "outputs": [],
   "source": [
    "pixel_means = X_train.mean(axis=0, keepdims=True)\n",
    "pixel_stds = X_train.std(axis=0, keepdims=True)\n",
    "X_train_scaled = (X_train - pixel_means) / pixel_stds\n",
    "X_valid_scaled = (X_valid - pixel_means) / pixel_stds\n",
    "X_test_scaled = (X_test - pixel_means) / pixel_stds"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 30,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Train on 55000 samples, validate on 5000 samples\n",
      "Epoch 1/5\n",
      "55000/55000 [==============================] - 35s 644us/sample - loss: 1.0197 - accuracy: 0.6154 - val_loss: 0.7386 - val_accuracy: 0.7348\n",
      "Epoch 2/5\n",
      "55000/55000 [==============================] - 33s 607us/sample - loss: 0.7149 - accuracy: 0.7401 - val_loss: 0.6187 - val_accuracy: 0.7774\n",
      "Epoch 3/5\n",
      "55000/55000 [==============================] - 32s 583us/sample - loss: 0.6193 - accuracy: 0.7803 - val_loss: 0.5926 - val_accuracy: 0.8036\n",
      "Epoch 4/5\n",
      "55000/55000 [==============================] - 32s 586us/sample - loss: 0.5555 - accuracy: 0.8043 - val_loss: 0.5208 - val_accuracy: 0.8262\n",
      "Epoch 5/5\n",
      "55000/55000 [==============================] - 32s 573us/sample - loss: 0.5159 - accuracy: 0.8238 - val_loss: 0.4790 - val_accuracy: 0.8358\n"
     ]
    }
   ],
   "source": [
    "history = model.fit(X_train_scaled, y_train, epochs=5,\n",
    "                    validation_data=(X_valid_scaled, y_valid))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Now look at what happens if we try to use the ReLU activation function instead:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 31,
   "metadata": {},
   "outputs": [],
   "source": [
    "np.random.seed(42)\n",
    "tf.random.set_seed(42)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 32,
   "metadata": {},
   "outputs": [],
   "source": [
    "model = keras.models.Sequential()\n",
    "model.add(keras.layers.Flatten(input_shape=[28, 28]))\n",
    "model.add(keras.layers.Dense(300, activation=\"relu\", kernel_initializer=\"he_normal\"))\n",
    "for layer in range(99):\n",
    "    model.add(keras.layers.Dense(100, activation=\"relu\", kernel_initializer=\"he_normal\"))\n",
    "model.add(keras.layers.Dense(10, activation=\"softmax\"))"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 33,
   "metadata": {},
   "outputs": [],
   "source": [
    "model.compile(loss=\"sparse_categorical_crossentropy\",\n",
    "              optimizer=keras.optimizers.SGD(lr=1e-3),\n",
    "              metrics=[\"accuracy\"])"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 34,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Train on 55000 samples, validate on 5000 samples\n",
      "Epoch 1/5\n",
      "55000/55000 [==============================] - 18s 319us/sample - loss: 1.9174 - accuracy: 0.2242 - val_loss: 1.3856 - val_accuracy: 0.3846\n",
      "Epoch 2/5\n",
      "55000/55000 [==============================] - 15s 279us/sample - loss: 1.2147 - accuracy: 0.4750 - val_loss: 1.0691 - val_accuracy: 0.5510\n",
      "Epoch 3/5\n",
      "55000/55000 [==============================] - 15s 281us/sample - loss: 0.9576 - accuracy: 0.6025 - val_loss: 0.7688 - val_accuracy: 0.7036\n",
      "Epoch 4/5\n",
      "55000/55000 [==============================] - 15s 281us/sample - loss: 0.8116 - accuracy: 0.6762 - val_loss: 0.7276 - val_accuracy: 0.7288\n",
      "Epoch 5/5\n",
      "55000/55000 [==============================] - 15s 278us/sample - loss: 0.8167 - accuracy: 0.6862 - val_loss: 0.7697 - val_accuracy: 0.7032\n"
     ]
    }
   ],
   "source": [
    "history = model.fit(X_train_scaled, y_train, epochs=5,\n",
    "                    validation_data=(X_valid_scaled, y_valid))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Not great at all, we suffered from the vanishing/exploding gradients problem."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# Batch Normalization"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 35,
   "metadata": {},
   "outputs": [],
   "source": [
    "model = keras.models.Sequential([\n",
    "    keras.layers.Flatten(input_shape=[28, 28]),\n",
    "    keras.layers.BatchNormalization(),\n",
    "    keras.layers.Dense(300, activation=\"relu\"),\n",
    "    keras.layers.BatchNormalization(),\n",
    "    keras.layers.Dense(100, activation=\"relu\"),\n",
    "    keras.layers.BatchNormalization(),\n",
    "    keras.layers.Dense(10, activation=\"softmax\")\n",
    "])"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 36,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Model: \"sequential_3\"\n",
      "_________________________________________________________________\n",
      "Layer (type)                 Output Shape              Param #   \n",
      "=================================================================\n",
      "flatten_3 (Flatten)          (None, 784)               0         \n",
      "_________________________________________________________________\n",
      "batch_normalization_v2 (Batc (None, 784)               3136      \n",
      "_________________________________________________________________\n",
      "dense_210 (Dense)            (None, 300)               235500    \n",
      "_________________________________________________________________\n",
      "batch_normalization_v2_1 (Ba (None, 300)               1200      \n",
      "_________________________________________________________________\n",
      "dense_211 (Dense)            (None, 100)               30100     \n",
      "_________________________________________________________________\n",
      "batch_normalization_v2_2 (Ba (None, 100)               400       \n",
      "_________________________________________________________________\n",
      "dense_212 (Dense)            (None, 10)                1010      \n",
      "=================================================================\n",
      "Total params: 271,346\n",
      "Trainable params: 268,978\n",
      "Non-trainable params: 2,368\n",
      "_________________________________________________________________\n"
     ]
    }
   ],
   "source": [
    "model.summary()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 37,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "[('batch_normalization_v2/gamma:0', True),\n",
       " ('batch_normalization_v2/beta:0', True),\n",
       " ('batch_normalization_v2/moving_mean:0', False),\n",
       " ('batch_normalization_v2/moving_variance:0', False)]"
      ]
     },
     "execution_count": 37,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "bn1 = model.layers[1]\n",
    "[(var.name, var.trainable) for var in bn1.variables]"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 38,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "ListWrapper([<tf.Operation 'batch_normalization_v2/cond_2/Identity' type=Identity>, <tf.Operation 'batch_normalization_v2/cond_3/Identity' type=Identity>])"
      ]
     },
     "execution_count": 38,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "bn1.updates"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 39,
   "metadata": {},
   "outputs": [],
   "source": [
    "model.compile(loss=\"sparse_categorical_crossentropy\",\n",
    "              optimizer=keras.optimizers.SGD(lr=1e-3),\n",
    "              metrics=[\"accuracy\"])"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 40,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Train on 55000 samples, validate on 5000 samples\n",
      "Epoch 1/10\n",
      "55000/55000 [==============================] - 5s 85us/sample - loss: 0.8756 - accuracy: 0.7140 - val_loss: 0.5514 - val_accuracy: 0.8212\n",
      "Epoch 2/10\n",
      "55000/55000 [==============================] - 4s 74us/sample - loss: 0.5765 - accuracy: 0.8033 - val_loss: 0.4742 - val_accuracy: 0.8436\n",
      "Epoch 3/10\n",
      "55000/55000 [==============================] - 4s 75us/sample - loss: 0.5146 - accuracy: 0.8216 - val_loss: 0.4382 - val_accuracy: 0.8530\n",
      "Epoch 4/10\n",
      "55000/55000 [==============================] - 4s 75us/sample - loss: 0.4821 - accuracy: 0.8322 - val_loss: 0.4170 - val_accuracy: 0.8604\n",
      "Epoch 5/10\n",
      "55000/55000 [==============================] - 4s 75us/sample - loss: 0.4589 - accuracy: 0.8402 - val_loss: 0.4003 - val_accuracy: 0.8658\n",
      "Epoch 6/10\n",
      "55000/55000 [==============================] - 4s 75us/sample - loss: 0.4428 - accuracy: 0.8459 - val_loss: 0.3883 - val_accuracy: 0.8698\n",
      "Epoch 7/10\n",
      "55000/55000 [==============================] - 4s 78us/sample - loss: 0.4220 - accuracy: 0.8521 - val_loss: 0.3792 - val_accuracy: 0.8720\n",
      "Epoch 8/10\n",
      "55000/55000 [==============================] - 4s 77us/sample - loss: 0.4150 - accuracy: 0.8546 - val_loss: 0.3696 - val_accuracy: 0.8754\n",
      "Epoch 9/10\n",
      "55000/55000 [==============================] - 4s 77us/sample - loss: 0.4013 - accuracy: 0.8589 - val_loss: 0.3629 - val_accuracy: 0.8746\n",
      "Epoch 10/10\n",
      "55000/55000 [==============================] - 4s 74us/sample - loss: 0.3931 - accuracy: 0.8615 - val_loss: 0.3581 - val_accuracy: 0.8766\n"
     ]
    }
   ],
   "source": [
    "history = model.fit(X_train, y_train, epochs=10,\n",
    "                    validation_data=(X_valid, y_valid))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Sometimes applying BN before the activation function works better (there's a debate on this topic). Moreover, the layer before a `BatchNormalization` layer does not need to have bias terms, since the `BatchNormalization` layer some as well, it would be a waste of parameters, so you can set `use_bias=False` when creating those layers:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 41,
   "metadata": {},
   "outputs": [],
   "source": [
    "model = keras.models.Sequential([\n",
    "    keras.layers.Flatten(input_shape=[28, 28]),\n",
    "    keras.layers.BatchNormalization(),\n",
    "    keras.layers.Dense(300, use_bias=False),\n",
    "    keras.layers.BatchNormalization(),\n",
    "    keras.layers.Activation(\"relu\"),\n",
    "    keras.layers.Dense(100, use_bias=False),\n",
    "    keras.layers.Activation(\"relu\"),\n",
    "    keras.layers.BatchNormalization(),\n",
    "    keras.layers.Dense(10, activation=\"softmax\")\n",
    "])"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 42,
   "metadata": {},
   "outputs": [],
   "source": [
    "model.compile(loss=\"sparse_categorical_crossentropy\",\n",
    "              optimizer=keras.optimizers.SGD(lr=1e-3),\n",
    "              metrics=[\"accuracy\"])"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 43,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Train on 55000 samples, validate on 5000 samples\n",
      "Epoch 1/10\n",
      "55000/55000 [==============================] - 5s 89us/sample - loss: 0.8617 - accuracy: 0.7095 - val_loss: 0.5649 - val_accuracy: 0.8102\n",
      "Epoch 2/10\n",
      "55000/55000 [==============================] - 4s 76us/sample - loss: 0.5803 - accuracy: 0.8015 - val_loss: 0.4833 - val_accuracy: 0.8344\n",
      "Epoch 3/10\n",
      "55000/55000 [==============================] - 4s 79us/sample - loss: 0.5153 - accuracy: 0.8208 - val_loss: 0.4463 - val_accuracy: 0.8462\n",
      "Epoch 4/10\n",
      "55000/55000 [==============================] - 4s 76us/sample - loss: 0.4846 - accuracy: 0.8307 - val_loss: 0.4256 - val_accuracy: 0.8530\n",
      "Epoch 5/10\n",
      "55000/55000 [==============================] - 4s 79us/sample - loss: 0.4576 - accuracy: 0.8402 - val_loss: 0.4106 - val_accuracy: 0.8590\n",
      "Epoch 6/10\n",
      "55000/55000 [==============================] - 4s 77us/sample - loss: 0.4401 - accuracy: 0.8467 - val_loss: 0.3973 - val_accuracy: 0.8610\n",
      "Epoch 7/10\n",
      "55000/55000 [==============================] - 4s 78us/sample - loss: 0.4296 - accuracy: 0.8482 - val_loss: 0.3899 - val_accuracy: 0.8650\n",
      "Epoch 8/10\n",
      "55000/55000 [==============================] - 4s 76us/sample - loss: 0.4127 - accuracy: 0.8559 - val_loss: 0.3818 - val_accuracy: 0.8658\n",
      "Epoch 9/10\n",
      "55000/55000 [==============================] - 4s 78us/sample - loss: 0.4007 - accuracy: 0.8588 - val_loss: 0.3741 - val_accuracy: 0.8682\n",
      "Epoch 10/10\n",
      "55000/55000 [==============================] - 4s 79us/sample - loss: 0.3929 - accuracy: 0.8621 - val_loss: 0.3694 - val_accuracy: 0.8734\n"
     ]
    }
   ],
   "source": [
    "history = model.fit(X_train, y_train, epochs=10,\n",
    "                    validation_data=(X_valid, y_valid))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Gradient Clipping"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "All Keras optimizers accept `clipnorm` or `clipvalue` arguments:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 44,
   "metadata": {},
   "outputs": [],
   "source": [
    "optimizer = keras.optimizers.SGD(clipvalue=1.0)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 45,
   "metadata": {},
   "outputs": [],
   "source": [
    "optimizer = keras.optimizers.SGD(clipnorm=1.0)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Reusing Pretrained Layers"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Reusing a Keras model"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Let's split the fashion MNIST training set in two:\n",
    "* `X_train_A`: all images of all items except for sandals and shirts (classes 5 and 6).\n",
    "* `X_train_B`: a much smaller training set of just the first 200 images of sandals or shirts.\n",
    "\n",
    "The validation set and the test set are also split this way, but without restricting the number of images.\n",
    "\n",
    "We will train a model on set A (classification task with 8 classes), and try to reuse it to tackle set B (binary classification). We hope to transfer a little bit of knowledge from task A to task B, since classes in set A (sneakers, ankle boots, coats, t-shirts, etc.) are somewhat similar to classes in set B (sandals and shirts). However, since we are using `Dense` layers, only patterns that occur at the same location can be reused (in contrast, convolutional layers will transfer much better, since learned patterns can be detected anywhere on the image, as we will see in the CNN chapter)."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 46,
   "metadata": {},
   "outputs": [],
   "source": [
    "def split_dataset(X, y):\n",
    "    y_5_or_6 = (y == 5) | (y == 6) # sandals or shirts\n",
    "    y_A = y[~y_5_or_6]\n",
    "    y_A[y_A > 6] -= 2 # class indices 7, 8, 9 should be moved to 5, 6, 7\n",
    "    y_B = (y[y_5_or_6] == 6).astype(np.float32) # binary classification task: is it a shirt (class 6)?\n",
    "    return ((X[~y_5_or_6], y_A),\n",
    "            (X[y_5_or_6], y_B))\n",
    "\n",
    "(X_train_A, y_train_A), (X_train_B, y_train_B) = split_dataset(X_train, y_train)\n",
    "(X_valid_A, y_valid_A), (X_valid_B, y_valid_B) = split_dataset(X_valid, y_valid)\n",
    "(X_test_A, y_test_A), (X_test_B, y_test_B) = split_dataset(X_test, y_test)\n",
    "X_train_B = X_train_B[:200]\n",
    "y_train_B = y_train_B[:200]"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 47,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "(43986, 28, 28)"
      ]
     },
     "execution_count": 47,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "X_train_A.shape"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 48,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "(200, 28, 28)"
      ]
     },
     "execution_count": 48,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "X_train_B.shape"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 49,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "array([4, 0, 5, 7, 7, 7, 4, 4, 3, 4, 0, 1, 6, 3, 4, 3, 2, 6, 5, 3, 4, 5,\n",
       "       1, 3, 4, 2, 0, 6, 7, 1], dtype=uint8)"
      ]
     },
     "execution_count": 49,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "y_train_A[:30]"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 50,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "array([1., 1., 0., 0., 0., 0., 1., 1., 1., 0., 0., 1., 1., 0., 0., 0., 0.,\n",
       "       0., 0., 1., 1., 0., 0., 1., 1., 0., 1., 1., 1., 1.], dtype=float32)"
      ]
     },
     "execution_count": 50,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "y_train_B[:30]"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 51,
   "metadata": {},
   "outputs": [],
   "source": [
    "tf.random.set_seed(42)\n",
    "np.random.seed(42)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 52,
   "metadata": {},
   "outputs": [],
   "source": [
    "model_A = keras.models.Sequential()\n",
    "model_A.add(keras.layers.Flatten(input_shape=[28, 28]))\n",
    "for n_hidden in (300, 100, 50, 50, 50):\n",
    "    model_A.add(keras.layers.Dense(n_hidden, activation=\"selu\"))\n",
    "model_A.add(keras.layers.Dense(8, activation=\"softmax\"))"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 53,
   "metadata": {},
   "outputs": [],
   "source": [
    "model_A.compile(loss=\"sparse_categorical_crossentropy\",\n",
    "                optimizer=keras.optimizers.SGD(lr=1e-3),\n",
    "                metrics=[\"accuracy\"])"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 54,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Train on 43986 samples, validate on 4014 samples\n",
      "Epoch 1/20\n",
      "43986/43986 [==============================] - 3s 78us/sample - loss: 0.5887 - accuracy: 0.8123 - val_loss: 0.3749 - val_accuracy: 0.8734\n",
      "Epoch 2/20\n",
      "43986/43986 [==============================] - 3s 69us/sample - loss: 0.3516 - accuracy: 0.8793 - val_loss: 0.3223 - val_accuracy: 0.8874\n",
      "Epoch 3/20\n",
      "43986/43986 [==============================] - 3s 68us/sample - loss: 0.3160 - accuracy: 0.8894 - val_loss: 0.3009 - val_accuracy: 0.8956\n",
      "Epoch 4/20\n",
      "43986/43986 [==============================] - 3s 70us/sample - loss: 0.2963 - accuracy: 0.8979 - val_loss: 0.2850 - val_accuracy: 0.9036\n",
      "Epoch 5/20\n",
      "43986/43986 [==============================] - 3s 68us/sample - loss: 0.2825 - accuracy: 0.9035 - val_loss: 0.2767 - val_accuracy: 0.9076\n",
      "Epoch 6/20\n",
      "43986/43986 [==============================] - 3s 69us/sample - loss: 0.2720 - accuracy: 0.9068 - val_loss: 0.2672 - val_accuracy: 0.9093\n",
      "Epoch 7/20\n",
      "43986/43986 [==============================] - 3s 72us/sample - loss: 0.2638 - accuracy: 0.9093 - val_loss: 0.2658 - val_accuracy: 0.9103\n",
      "Epoch 8/20\n",
      "43986/43986 [==============================] - 3s 70us/sample - loss: 0.2570 - accuracy: 0.9120 - val_loss: 0.2592 - val_accuracy: 0.9106\n",
      "Epoch 9/20\n",
      "43986/43986 [==============================] - 3s 71us/sample - loss: 0.2514 - accuracy: 0.9139 - val_loss: 0.2570 - val_accuracy: 0.9128\n",
      "Epoch 10/20\n",
      "43986/43986 [==============================] - 3s 72us/sample - loss: 0.2465 - accuracy: 0.9166 - val_loss: 0.2557 - val_accuracy: 0.9108\n",
      "Epoch 11/20\n",
      "43986/43986 [==============================] - 3s 69us/sample - loss: 0.2418 - accuracy: 0.9178 - val_loss: 0.2484 - val_accuracy: 0.9178\n",
      "Epoch 12/20\n",
      "43986/43986 [==============================] - 3s 70us/sample - loss: 0.2379 - accuracy: 0.9192 - val_loss: 0.2461 - val_accuracy: 0.9178\n",
      "Epoch 13/20\n",
      "43986/43986 [==============================] - 3s 71us/sample - loss: 0.2342 - accuracy: 0.9199 - val_loss: 0.2425 - val_accuracy: 0.9188\n",
      "Epoch 14/20\n",
      "43986/43986 [==============================] - 3s 68us/sample - loss: 0.2313 - accuracy: 0.9215 - val_loss: 0.2412 - val_accuracy: 0.9185\n",
      "Epoch 15/20\n",
      "43986/43986 [==============================] - 3s 68us/sample - loss: 0.2280 - accuracy: 0.9222 - val_loss: 0.2382 - val_accuracy: 0.9173\n",
      "Epoch 16/20\n",
      "43986/43986 [==============================] - 3s 71us/sample - loss: 0.2252 - accuracy: 0.9224 - val_loss: 0.2360 - val_accuracy: 0.9205\n",
      "Epoch 17/20\n",
      "43986/43986 [==============================] - 3s 71us/sample - loss: 0.2229 - accuracy: 0.9232 - val_loss: 0.2419 - val_accuracy: 0.9158\n",
      "Epoch 18/20\n",
      "43986/43986 [==============================] - 3s 71us/sample - loss: 0.2195 - accuracy: 0.9249 - val_loss: 0.2357 - val_accuracy: 0.9170\n",
      "Epoch 19/20\n",
      "43986/43986 [==============================] - 3s 68us/sample - loss: 0.2177 - accuracy: 0.9254 - val_loss: 0.2331 - val_accuracy: 0.9200\n",
      "Epoch 20/20\n",
      "43986/43986 [==============================] - 3s 70us/sample - loss: 0.2154 - accuracy: 0.9260 - val_loss: 0.2372 - val_accuracy: 0.9158\n"
     ]
    }
   ],
   "source": [
    "history = model_A.fit(X_train_A, y_train_A, epochs=20,\n",
    "                    validation_data=(X_valid_A, y_valid_A))"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 55,
   "metadata": {},
   "outputs": [],
   "source": [
    "model_A.save(\"my_model_A.h5\")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 56,
   "metadata": {},
   "outputs": [],
   "source": [
    "model_B = keras.models.Sequential()\n",
    "model_B.add(keras.layers.Flatten(input_shape=[28, 28]))\n",
    "for n_hidden in (300, 100, 50, 50, 50):\n",
    "    model_B.add(keras.layers.Dense(n_hidden, activation=\"selu\"))\n",
    "model_B.add(keras.layers.Dense(1, activation=\"sigmoid\"))"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 57,
   "metadata": {},
   "outputs": [],
   "source": [
    "model_B.compile(loss=\"binary_crossentropy\",\n",
    "                optimizer=keras.optimizers.SGD(lr=1e-3),\n",
    "                metrics=[\"accuracy\"])"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 58,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Train on 200 samples, validate on 986 samples\n",
      "Epoch 1/20\n",
      "200/200 [==============================] - 0s 2ms/sample - loss: 0.9537 - accuracy: 0.4800 - val_loss: 0.6472 - val_accuracy: 0.5710\n",
      "Epoch 2/20\n",
      "200/200 [==============================] - 0s 318us/sample - loss: 0.5805 - accuracy: 0.6850 - val_loss: 0.4863 - val_accuracy: 0.8428\n",
      "Epoch 3/20\n",
      "200/200 [==============================] - 0s 318us/sample - loss: 0.4561 - accuracy: 0.8750 - val_loss: 0.4116 - val_accuracy: 0.8905\n",
      "Epoch 4/20\n",
      "200/200 [==============================] - 0s 308us/sample - loss: 0.3885 - accuracy: 0.9100 - val_loss: 0.3650 - val_accuracy: 0.9148\n",
      "Epoch 5/20\n",
      "200/200 [==============================] - 0s 311us/sample - loss: 0.3426 - accuracy: 0.9250 - val_loss: 0.3308 - val_accuracy: 0.9270\n",
      "Epoch 6/20\n",
      "200/200 [==============================] - 0s 317us/sample - loss: 0.3084 - accuracy: 0.9300 - val_loss: 0.3044 - val_accuracy: 0.9371\n",
      "Epoch 7/20\n",
      "200/200 [==============================] - 0s 309us/sample - loss: 0.2810 - accuracy: 0.9400 - val_loss: 0.2806 - val_accuracy: 0.9432\n",
      "Epoch 8/20\n",
      "200/200 [==============================] - 0s 313us/sample - loss: 0.2572 - accuracy: 0.9500 - val_loss: 0.2607 - val_accuracy: 0.9462\n",
      "Epoch 9/20\n",
      "200/200 [==============================] - 0s 312us/sample - loss: 0.2372 - accuracy: 0.9600 - val_loss: 0.2439 - val_accuracy: 0.9513\n",
      "Epoch 10/20\n",
      "200/200 [==============================] - 0s 319us/sample - loss: 0.2202 - accuracy: 0.9600 - val_loss: 0.2290 - val_accuracy: 0.9523\n",
      "Epoch 11/20\n",
      "200/200 [==============================] - 0s 315us/sample - loss: 0.2047 - accuracy: 0.9650 - val_loss: 0.2161 - val_accuracy: 0.9564\n",
      "Epoch 12/20\n",
      "200/200 [==============================] - 0s 325us/sample - loss: 0.1917 - accuracy: 0.9700 - val_loss: 0.2046 - val_accuracy: 0.9584\n",
      "Epoch 13/20\n",
      "200/200 [==============================] - 0s 335us/sample - loss: 0.1798 - accuracy: 0.9750 - val_loss: 0.1944 - val_accuracy: 0.9604\n",
      "Epoch 14/20\n",
      "200/200 [==============================] - 0s 319us/sample - loss: 0.1690 - accuracy: 0.9750 - val_loss: 0.1860 - val_accuracy: 0.9604\n",
      "Epoch 15/20\n",
      "200/200 [==============================] - 0s 319us/sample - loss: 0.1594 - accuracy: 0.9850 - val_loss: 0.1774 - val_accuracy: 0.9635\n",
      "Epoch 16/20\n",
      "200/200 [==============================] - 0s 343us/sample - loss: 0.1508 - accuracy: 0.9850 - val_loss: 0.1691 - val_accuracy: 0.9675\n",
      "Epoch 17/20\n",
      "200/200 [==============================] - 0s 328us/sample - loss: 0.1426 - accuracy: 0.9900 - val_loss: 0.1621 - val_accuracy: 0.9686\n",
      "Epoch 18/20\n",
      "200/200 [==============================] - 0s 340us/sample - loss: 0.1355 - accuracy: 0.9900 - val_loss: 0.1558 - val_accuracy: 0.9706\n",
      "Epoch 19/20\n",
      "200/200 [==============================] - 0s 306us/sample - loss: 0.1288 - accuracy: 0.9900 - val_loss: 0.1505 - val_accuracy: 0.9706\n",
      "Epoch 20/20\n",
      "200/200 [==============================] - 0s 312us/sample - loss: 0.1230 - accuracy: 0.9900 - val_loss: 0.1454 - val_accuracy: 0.9716\n"
     ]
    }
   ],
   "source": [
    "history = model_B.fit(X_train_B, y_train_B, epochs=20,\n",
    "                      validation_data=(X_valid_B, y_valid_B))"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 59,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Model: \"sequential_4\"\n",
      "_________________________________________________________________\n",
      "Layer (type)                 Output Shape              Param #   \n",
      "=================================================================\n",
      "flatten_4 (Flatten)          (None, 784)               0         \n",
      "_________________________________________________________________\n",
      "batch_normalization_v2_3 (Ba (None, 784)               3136      \n",
      "_________________________________________________________________\n",
      "dense_213 (Dense)            (None, 300)               235500    \n",
      "_________________________________________________________________\n",
      "batch_normalization_v2_4 (Ba (None, 300)               1200      \n",
      "_________________________________________________________________\n",
      "activation (Activation)      (None, 300)               0         \n",
      "_________________________________________________________________\n",
      "dense_214 (Dense)            (None, 100)               30100     \n",
      "_________________________________________________________________\n",
      "activation_1 (Activation)    (None, 100)               0         \n",
      "_________________________________________________________________\n",
      "batch_normalization_v2_5 (Ba (None, 100)               400       \n",
      "_________________________________________________________________\n",
      "dense_215 (Dense)            (None, 10)                1010      \n",
      "=================================================================\n",
      "Total params: 271,346\n",
      "Trainable params: 268,978\n",
      "Non-trainable params: 2,368\n",
      "_________________________________________________________________\n"
     ]
    }
   ],
   "source": [
    "model.summary()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 60,
   "metadata": {},
   "outputs": [],
   "source": [
    "model_A = keras.models.load_model(\"my_model_A.h5\")\n",
    "model_B_on_A = keras.models.Sequential(model_A.layers[:-1])\n",
    "model_B_on_A.add(keras.layers.Dense(1, activation=\"sigmoid\"))"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 61,
   "metadata": {},
   "outputs": [],
   "source": [
    "model_A_clone = keras.models.clone_model(model_A)\n",
    "model_A_clone.set_weights(model_A.get_weights())"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 62,
   "metadata": {},
   "outputs": [],
   "source": [
    "for layer in model_B_on_A.layers[:-1]:\n",
    "    layer.trainable = False\n",
    "\n",
    "model_B_on_A.compile(loss=\"binary_crossentropy\",\n",
    "                     optimizer=keras.optimizers.SGD(lr=1e-3),\n",
    "                     metrics=[\"accuracy\"])"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 63,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Train on 200 samples, validate on 986 samples\n",
      "Epoch 1/4\n",
      "200/200 [==============================] - 0s 2ms/sample - loss: 0.5851 - accuracy: 0.6600 - val_loss: 0.5855 - val_accuracy: 0.6318\n",
      "Epoch 2/4\n",
      "200/200 [==============================] - 0s 303us/sample - loss: 0.5484 - accuracy: 0.6850 - val_loss: 0.5484 - val_accuracy: 0.6775\n",
      "Epoch 3/4\n",
      "200/200 [==============================] - 0s 294us/sample - loss: 0.5116 - accuracy: 0.7250 - val_loss: 0.5141 - val_accuracy: 0.7160\n",
      "Epoch 4/4\n",
      "200/200 [==============================] - 0s 316us/sample - loss: 0.4779 - accuracy: 0.7450 - val_loss: 0.4859 - val_accuracy: 0.7363\n",
      "Train on 200 samples, validate on 986 samples\n",
      "Epoch 1/16\n",
      "200/200 [==============================] - 0s 2ms/sample - loss: 0.3989 - accuracy: 0.8050 - val_loss: 0.3419 - val_accuracy: 0.8702\n",
      "Epoch 2/16\n",
      "200/200 [==============================] - 0s 328us/sample - loss: 0.2795 - accuracy: 0.9300 - val_loss: 0.2624 - val_accuracy: 0.9280\n",
      "Epoch 3/16\n",
      "200/200 [==============================] - 0s 319us/sample - loss: 0.2128 - accuracy: 0.9650 - val_loss: 0.2150 - val_accuracy: 0.9544\n",
      "Epoch 4/16\n",
      "200/200 [==============================] - 0s 318us/sample - loss: 0.1720 - accuracy: 0.9800 - val_loss: 0.1826 - val_accuracy: 0.9635\n",
      "Epoch 5/16\n",
      "200/200 [==============================] - 0s 317us/sample - loss: 0.1436 - accuracy: 0.9800 - val_loss: 0.1586 - val_accuracy: 0.9736\n",
      "Epoch 6/16\n",
      "200/200 [==============================] - 0s 317us/sample - loss: 0.1231 - accuracy: 0.9850 - val_loss: 0.1407 - val_accuracy: 0.9807\n",
      "Epoch 7/16\n",
      "200/200 [==============================] - 0s 325us/sample - loss: 0.1074 - accuracy: 0.9900 - val_loss: 0.1270 - val_accuracy: 0.9828\n",
      "Epoch 8/16\n",
      "200/200 [==============================] - 0s 326us/sample - loss: 0.0953 - accuracy: 0.9950 - val_loss: 0.1158 - val_accuracy: 0.9848\n",
      "Epoch 9/16\n",
      "200/200 [==============================] - 0s 319us/sample - loss: 0.0854 - accuracy: 1.0000 - val_loss: 0.1076 - val_accuracy: 0.9878\n",
      "Epoch 10/16\n",
      "200/200 [==============================] - 0s 322us/sample - loss: 0.0781 - accuracy: 1.0000 - val_loss: 0.1007 - val_accuracy: 0.9888\n",
      "Epoch 11/16\n",
      "200/200 [==============================] - 0s 316us/sample - loss: 0.0718 - accuracy: 1.0000 - val_loss: 0.0944 - val_accuracy: 0.9888\n",
      "Epoch 12/16\n",
      "200/200 [==============================] - 0s 319us/sample - loss: 0.0662 - accuracy: 1.0000 - val_loss: 0.0891 - val_accuracy: 0.9899\n",
      "Epoch 13/16\n",
      "200/200 [==============================] - 0s 318us/sample - loss: 0.0613 - accuracy: 1.0000 - val_loss: 0.0846 - val_accuracy: 0.9899\n",
      "Epoch 14/16\n",
      "200/200 [==============================] - 0s 332us/sample - loss: 0.0574 - accuracy: 1.0000 - val_loss: 0.0806 - val_accuracy: 0.9899\n",
      "Epoch 15/16\n",
      "200/200 [==============================] - 0s 320us/sample - loss: 0.0538 - accuracy: 1.0000 - val_loss: 0.0770 - val_accuracy: 0.9899\n",
      "Epoch 16/16\n",
      "200/200 [==============================] - 0s 320us/sample - loss: 0.0505 - accuracy: 1.0000 - val_loss: 0.0740 - val_accuracy: 0.9899\n"
     ]
    }
   ],
   "source": [
    "history = model_B_on_A.fit(X_train_B, y_train_B, epochs=4,\n",
    "                           validation_data=(X_valid_B, y_valid_B))\n",
    "\n",
    "for layer in model_B_on_A.layers[:-1]:\n",
    "    layer.trainable = True\n",
    "\n",
    "model_B_on_A.compile(loss=\"binary_crossentropy\",\n",
    "                     optimizer=keras.optimizers.SGD(lr=1e-3),\n",
    "                     metrics=[\"accuracy\"])\n",
    "history = model_B_on_A.fit(X_train_B, y_train_B, epochs=16,\n",
    "                           validation_data=(X_valid_B, y_valid_B))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "So, what's the final verdict?"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 64,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "2000/2000 [==============================] - 0s 41us/sample - loss: 0.1431 - accuracy: 0.9705\n"
     ]
    },
    {
     "data": {
      "text/plain": [
       "[0.1430660070180893, 0.9705]"
      ]
     },
     "execution_count": 64,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "model_B.evaluate(X_test_B, y_test_B)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 65,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "2000/2000 [==============================] - 0s 38us/sample - loss: 0.0689 - accuracy: 0.9925\n"
     ]
    },
    {
     "data": {
      "text/plain": [
       "[0.06887910133600235, 0.9925]"
      ]
     },
     "execution_count": 65,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "model_B_on_A.evaluate(X_test_B, y_test_B)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Great! We got quite a bit of transfer: the error rate dropped by a factor of almost 4!"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 66,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "3.933333333333337"
      ]
     },
     "execution_count": 66,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "(100 - 97.05) / (100 - 99.25)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# Faster Optimizers"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Momentum optimization"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 67,
   "metadata": {},
   "outputs": [],
   "source": [
    "optimizer = keras.optimizers.SGD(lr=0.001, momentum=0.9)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Nesterov Accelerated Gradient"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 68,
   "metadata": {},
   "outputs": [],
   "source": [
    "optimizer = keras.optimizers.SGD(lr=0.001, momentum=0.9, nesterov=True)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## AdaGrad"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 69,
   "metadata": {},
   "outputs": [],
   "source": [
    "optimizer = keras.optimizers.Adagrad(lr=0.001)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## RMSProp"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 70,
   "metadata": {},
   "outputs": [],
   "source": [
    "optimizer = keras.optimizers.RMSprop(lr=0.001, rho=0.9)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Adam Optimization"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 71,
   "metadata": {},
   "outputs": [],
   "source": [
    "optimizer = keras.optimizers.Adam(lr=0.001, beta_1=0.9, beta_2=0.999)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Adamax Optimization"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 72,
   "metadata": {},
   "outputs": [],
   "source": [
    "optimizer = keras.optimizers.Adamax(lr=0.001, beta_1=0.9, beta_2=0.999)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Nadam Optimization"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 73,
   "metadata": {},
   "outputs": [],
   "source": [
    "optimizer = keras.optimizers.Nadam(lr=0.001, beta_1=0.9, beta_2=0.999)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Learning Rate Scheduling"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Power Scheduling"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "```lr = lr0 / (1 + steps / s)**c```\n",
    "* Keras uses `c=1` and `s = 1 / decay`"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 74,
   "metadata": {},
   "outputs": [],
   "source": [
    "optimizer = keras.optimizers.SGD(lr=0.01, decay=1e-4)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 75,
   "metadata": {},
   "outputs": [],
   "source": [
    "model = keras.models.Sequential([\n",
    "    keras.layers.Flatten(input_shape=[28, 28]),\n",
    "    keras.layers.Dense(300, activation=\"selu\", kernel_initializer=\"lecun_normal\"),\n",
    "    keras.layers.Dense(100, activation=\"selu\", kernel_initializer=\"lecun_normal\"),\n",
    "    keras.layers.Dense(10, activation=\"softmax\")\n",
    "])\n",
    "model.compile(loss=\"sparse_categorical_crossentropy\", optimizer=optimizer, metrics=[\"accuracy\"])"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 76,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Train on 55000 samples, validate on 5000 samples\n",
      "Epoch 1/25\n",
      "55000/55000 [==============================] - 4s 66us/sample - loss: 0.4840 - accuracy: 0.8296 - val_loss: 0.4038 - val_accuracy: 0.8630\n",
      "Epoch 2/25\n",
      "55000/55000 [==============================] - 3s 63us/sample - loss: 0.3787 - accuracy: 0.8653 - val_loss: 0.3846 - val_accuracy: 0.8706\n",
      "Epoch 3/25\n",
      "55000/55000 [==============================] - 3s 62us/sample - loss: 0.3461 - accuracy: 0.8770 - val_loss: 0.3606 - val_accuracy: 0.8776\n",
      "Epoch 4/25\n",
      "55000/55000 [==============================] - 3s 63us/sample - loss: 0.3248 - accuracy: 0.8844 - val_loss: 0.3661 - val_accuracy: 0.8738\n",
      "Epoch 5/25\n",
      "55000/55000 [==============================] - 3s 62us/sample - loss: 0.3092 - accuracy: 0.8902 - val_loss: 0.3516 - val_accuracy: 0.8792\n",
      "Epoch 6/25\n",
      "55000/55000 [==============================] - 3s 63us/sample - loss: 0.2967 - accuracy: 0.8938 - val_loss: 0.3467 - val_accuracy: 0.8810\n",
      "Epoch 7/25\n",
      "55000/55000 [==============================] - 3s 63us/sample - loss: 0.2862 - accuracy: 0.8967 - val_loss: 0.3398 - val_accuracy: 0.8844\n",
      "Epoch 8/25\n",
      "55000/55000 [==============================] - 3s 61us/sample - loss: 0.2771 - accuracy: 0.8997 - val_loss: 0.3384 - val_accuracy: 0.8832\n",
      "Epoch 9/25\n",
      "55000/55000 [==============================] - 3s 62us/sample - loss: 0.2696 - accuracy: 0.9035 - val_loss: 0.3345 - val_accuracy: 0.8860\n",
      "Epoch 10/25\n",
      "55000/55000 [==============================] - 3s 62us/sample - loss: 0.2628 - accuracy: 0.9057 - val_loss: 0.3343 - val_accuracy: 0.8830\n",
      "Epoch 11/25\n",
      "55000/55000 [==============================] - 3s 61us/sample - loss: 0.2568 - accuracy: 0.9083 - val_loss: 0.3290 - val_accuracy: 0.8882\n",
      "Epoch 12/25\n",
      "55000/55000 [==============================] - 3s 62us/sample - loss: 0.2510 - accuracy: 0.9099 - val_loss: 0.3243 - val_accuracy: 0.8904\n",
      "Epoch 13/25\n",
      "55000/55000 [==============================] - 3s 61us/sample - loss: 0.2459 - accuracy: 0.9118 - val_loss: 0.3271 - val_accuracy: 0.8874\n",
      "Epoch 14/25\n",
      "55000/55000 [==============================] - 3s 62us/sample - loss: 0.2415 - accuracy: 0.9130 - val_loss: 0.3259 - val_accuracy: 0.8886\n",
      "Epoch 15/25\n",
      "55000/55000 [==============================] - 3s 62us/sample - loss: 0.2370 - accuracy: 0.9157 - val_loss: 0.3249 - val_accuracy: 0.8896\n",
      "Epoch 16/25\n",
      "55000/55000 [==============================] - 3s 61us/sample - loss: 0.2332 - accuracy: 0.9177 - val_loss: 0.3267 - val_accuracy: 0.8892\n",
      "Epoch 17/25\n",
      "55000/55000 [==============================] - 3s 63us/sample - loss: 0.2296 - accuracy: 0.9177 - val_loss: 0.3251 - val_accuracy: 0.8880\n",
      "Epoch 18/25\n",
      "55000/55000 [==============================] - 3s 61us/sample - loss: 0.2257 - accuracy: 0.9194 - val_loss: 0.3221 - val_accuracy: 0.8900\n",
      "Epoch 19/25\n",
      "55000/55000 [==============================] - 3s 61us/sample - loss: 0.2228 - accuracy: 0.9212 - val_loss: 0.3237 - val_accuracy: 0.8910\n",
      "Epoch 20/25\n",
      "55000/55000 [==============================] - 3s 60us/sample - loss: 0.2198 - accuracy: 0.9223 - val_loss: 0.3217 - val_accuracy: 0.8904\n",
      "Epoch 21/25\n",
      "55000/55000 [==============================] - 3s 63us/sample - loss: 0.2166 - accuracy: 0.9238 - val_loss: 0.3185 - val_accuracy: 0.8938\n",
      "Epoch 22/25\n",
      "55000/55000 [==============================] - 3s 61us/sample - loss: 0.2140 - accuracy: 0.9252 - val_loss: 0.3212 - val_accuracy: 0.8902\n",
      "Epoch 23/25\n",
      "55000/55000 [==============================] - 3s 62us/sample - loss: 0.2113 - accuracy: 0.9256 - val_loss: 0.3235 - val_accuracy: 0.8898\n",
      "Epoch 24/25\n",
      "55000/55000 [==============================] - 3s 62us/sample - loss: 0.2088 - accuracy: 0.9262 - val_loss: 0.3216 - val_accuracy: 0.8930\n",
      "Epoch 25/25\n",
      "55000/55000 [==============================] - 3s 62us/sample - loss: 0.2061 - accuracy: 0.9273 - val_loss: 0.3199 - val_accuracy: 0.8922\n"
     ]
    }
   ],
   "source": [
    "n_epochs = 25\n",
    "history = model.fit(X_train_scaled, y_train, epochs=n_epochs,\n",
    "                    validation_data=(X_valid_scaled, y_valid))"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 77,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "image/png": "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vuvuXyTo64iINAl70iOKHG23Ks59ktmzAfgpkA2sBf4BXO7u88zsyPDyWJWHgJeAOcBcYFJYRji8eRTBJbKNwEXAqLAc4LdAR2CGmZWEr90GB4iIyJ4bNbSAaTcczbbViz+Kp31Sn7Nx9w0EiSK6/B2CG/9V2w5cH75iHWcmEHNst7v3qpdgRUSk3iS7ZyMiIs2Qko2IiCScko2IiCScko2IiCScko2IiCScko2IiCScko2IiCScko2IiCScko2IiCScko2IiCScko2IiCScko2IiCScko2IiCScko2IiCScko2IiCScko2IiCScko2IiCScko2IiCScko2IiCScko2IiCScko2IiCScko2IiCScko2IiCScko2IiCScko2IiCScko2IiCScko2IiCScko2IiCScko2IiCScko2IiCScko2IiCScko2IiCScko2IiCScko2IiCRcUpONmXUwswlmtsXMlpnZj6ppZ2Z2h5mtD193mJlF1A8xs4/MbGv4c0i8+4qISPIlu2dzH7ANyAPOAR4wswEx2o0GRgGDgUHAKcBlAGaWBUwEngLaA48DE8PyGvcVEZHUSFqyMbMc4EzgZncvcfepwIvAeTGaXwDc7e4r3L0IuBu4MKwbCWQA97h7ubvfCxhwdBz7iohICmQk8bP2B7a7+8KIslnAUTHaDgjrItsNiKib7e4eUT87LH+tln13YWajCXpCAOVmNje+r9KsdALWpTqIBkjnZXc6J7E19fPSI55GyUw2rYFNUWXFQJtq2hZHtWsd3nuJros+TrX7RiUo3P1h4GEAM/vQ3Q+K/+s0Dzovsem87E7nJDadl0Ay79mUALlRZbnA5jja5gIlYbKo7Tg17SsiIimQzGSzEMgws94RZYOBeTHazgvrYrWbBwyKGmE2KKq+un1FRCQFkpZs3H0LMB641cxyzGwEcBrwZIzmTwBXm1mBmeUD1wBjw7pCoBK40sxamNkVYfmUOPatycN1/1bNgs5LbDovu9M5iU3nBbBkXl0ysw7AY8BxwHrgBnd/xsyOBF5199ZhOwPuAC4Jd30E+HnVpTAzGxqW9QfmAxe7+8x49hURkeRLarIREZHmSdPViIhIwinZiIhIwjX7ZBPvfG3NjZkVmlmZmZWErwWpjinZzOwKM/vQzMrNbGxU3TFm9lk4P99bZhbXg21NQXXnxcx6mplH/M6UmNnNKQw1qcIBS4+Gf0c2m9knZnZSRH2z/Z0BJRuIf7625ugKd28dvvqkOpgUWAn8lmBQy05m1olgZOXNQAfgQ2Bc0qNLnZjnJUK7iN+b3yQxrlTLAL4kmBWlLfBL4LkwCTf335mkziDQ4ETM1zbQ3UuAqWZWNV/bDSkNTlLO3ccDmNlBwN4RVWcA89z9n2H9GGCdmfV198+SHmiS1XBemrXw8Y4xEUUvm9kSYBjQkWb8OwPq2VQ3X5t6NoHbzWydmU0zs5GpDqYB2WX+vfCPzOfo96bKMjNbYWZ/D/9F3yyZWR7B35h56Hem2SebuszX1tz8HNgHKCB4KO0lM9s3tSE1GLXNz9dcrQMOJpiYcRjB+Xg6pRGliJllEnz3x8OeS7P/nWnuyaYu87U1K+4+3d03h8s4PA5MA76X6rgaCP3exBAuHfKhu2939zXAFcDxZtZs/qACmFkawcwo2wjOAeh3ptknm7rM19bcOcG6QRI1/154729f9HsTreqJ8WbzdyacweRRggFHZ7p7RVjV7H9nms0vQSx1nK+t2TCzdmZ2gpm1NLMMMzsH+A7BekHNRvjdWwLpQHrV+QAmAAPN7Myw/lcEayw1ixu91Z0XMzvUzPqYWZqZdQTuBQrdPfryUVP2ANAPOMXdSyPKm/XvDADu3qxfBMMQXwC2AMuBH6U6plS/gM7ADIIu/kbgfeC4VMeVgvMwhuBf55GvMWHdscBnQCnB5LA9Ux1vqs8L8ENgSfj/0iqCSXG7pDreJJ6XHuG5KCO4bFb1Oqe5/864u+ZGExGRxGvWl9FERCQ5lGxERCThlGxERCThlGxERCThlGxERCThlGxERCThlGxEmqhwbZnvpzoOEVCyEUkIMxsb/rGPfr2f6thEUqFZr2cjkmBvEKyNFGlbKgIRSTX1bEQSp9zdV0e9NsDOS1xXmNmkcJngZWZ2buTOZnaAmb1hZqVmtiHsLbWNanOBmc0Jl2heY2aPR8XQwcz+GS57/kX0Z4gki5KNSOrcArwIDCFYM+iJcPXLqlmBJxPMrXUIcDownIilmM3sMuAh4O/AIIIlIOZGfcavgIkEMw6PAx4zs+6J+0oisWluNJEEMLOxwLkEkzJGus/df25mDjzi7pdG7PMGsNrdzzWzS4G7gL3dfXNYPxJ4C+jt7ovNbAXwlLvHXMI8/Izfu/uN4XYGwWKBo939qXr8uiK10j0bkcR5GxgdVbYx4v17UXXvAf8Vvu9HMAV95OJa7wI7gP5mtolgFdU3a4lhdtUbd99uZl8Be8UXvkj9UbIRSZyt7r44Acety+WIiqhtR5fPJQX0SyeSOofF2J4fvp8PHBC1pPJwgv9n57v7WqAIOCbhUYrUA/VsRBKnhZl1iSqrdPevwvdnmNkMgoW0vk+QOA4N654mGEDwhJn9CmhPMBhgfERv6TbgT2a2BpgEtAKOcfe7E/WFRPaUko1I4hxLsGJlpCJg7/D9GOBMguWTvwJ+7O4zANx9q5mdANwDfEAw0GAi8LOqA7n7A2a2DbgGuAPYALySqC8j8m1oNJpICoQjxc5y93+lOhaRZNA9GxERSTglGxERSThdRhMRkYRTz0ZERBJOyUZERBJOyUZERBJOyUZERBJOyUZERBLu/wFIBub2peel9wAAAABJRU5ErkJggg==\n",
      "text/plain": [
       "<Figure size 432x288 with 1 Axes>"
      ]
     },
     "metadata": {
      "needs_background": "light"
     },
     "output_type": "display_data"
    }
   ],
   "source": [
    "learning_rate = 0.01\n",
    "decay = 1e-4\n",
    "batch_size = 32\n",
    "n_steps_per_epoch = len(X_train) // batch_size\n",
    "epochs = np.arange(n_epochs)\n",
    "lrs = learning_rate / (1 + decay * epochs * n_steps_per_epoch)\n",
    "\n",
    "plt.plot(epochs, lrs,  \"o-\")\n",
    "plt.axis([0, n_epochs - 1, 0, 0.01])\n",
    "plt.xlabel(\"Epoch\")\n",
    "plt.ylabel(\"Learning Rate\")\n",
    "plt.title(\"Power Scheduling\", fontsize=14)\n",
    "plt.grid(True)\n",
    "plt.show()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Exponential Scheduling"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "```lr = lr0 * 0.1**(epoch / s)```"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 78,
   "metadata": {},
   "outputs": [],
   "source": [
    "def exponential_decay_fn(epoch):\n",
    "    return 0.01 * 0.1**(epoch / 20)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 79,
   "metadata": {},
   "outputs": [],
   "source": [
    "def exponential_decay(lr0, s):\n",
    "    def exponential_decay_fn(epoch):\n",
    "        return lr0 * 0.1**(epoch / s)\n",
    "    return exponential_decay_fn\n",
    "\n",
    "exponential_decay_fn = exponential_decay(lr0=0.01, s=20)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 80,
   "metadata": {},
   "outputs": [],
   "source": [
    "model = keras.models.Sequential([\n",
    "    keras.layers.Flatten(input_shape=[28, 28]),\n",
    "    keras.layers.Dense(300, activation=\"selu\", kernel_initializer=\"lecun_normal\"),\n",
    "    keras.layers.Dense(100, activation=\"selu\", kernel_initializer=\"lecun_normal\"),\n",
    "    keras.layers.Dense(10, activation=\"softmax\")\n",
    "])\n",
    "model.compile(loss=\"sparse_categorical_crossentropy\", optimizer=\"nadam\", metrics=[\"accuracy\"])\n",
    "n_epochs = 25"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 81,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Train on 55000 samples, validate on 5000 samples\n",
      "Epoch 1/25\n",
      "55000/55000 [==============================] - 6s 107us/sample - loss: 0.8245 - accuracy: 0.7595 - val_loss: 1.0870 - val_accuracy: 0.7106\n",
      "Epoch 2/25\n",
      "55000/55000 [==============================] - 6s 101us/sample - loss: 0.6391 - accuracy: 0.8064 - val_loss: 0.6125 - val_accuracy: 0.8160\n",
      "Epoch 3/25\n",
      "55000/55000 [==============================] - 6s 101us/sample - loss: 0.5962 - accuracy: 0.8174 - val_loss: 0.6526 - val_accuracy: 0.8086\n",
      "Epoch 4/25\n",
      "55000/55000 [==============================] - 5s 99us/sample - loss: 0.5420 - accuracy: 0.8306 - val_loss: 0.7521 - val_accuracy: 0.7766\n",
      "Epoch 5/25\n",
      "55000/55000 [==============================] - 5s 100us/sample - loss: 0.4853 - accuracy: 0.8460 - val_loss: 0.5616 - val_accuracy: 0.8314\n",
      "Epoch 6/25\n",
      "55000/55000 [==============================] - 5s 98us/sample - loss: 0.4443 - accuracy: 0.8571 - val_loss: 0.5430 - val_accuracy: 0.8664\n",
      "Epoch 7/25\n",
      "55000/55000 [==============================] - 5s 99us/sample - loss: 0.4128 - accuracy: 0.8687 - val_loss: 0.4954 - val_accuracy: 0.8610\n",
      "Epoch 8/25\n",
      "55000/55000 [==============================] - 6s 100us/sample - loss: 0.3763 - accuracy: 0.8773 - val_loss: 0.5770 - val_accuracy: 0.8578\n",
      "Epoch 9/25\n",
      "55000/55000 [==============================] - 6s 102us/sample - loss: 0.3459 - accuracy: 0.8847 - val_loss: 0.5267 - val_accuracy: 0.8688\n",
      "Epoch 10/25\n",
      "55000/55000 [==============================] - 5s 99us/sample - loss: 0.3250 - accuracy: 0.8931 - val_loss: 0.4606 - val_accuracy: 0.8644\n",
      "Epoch 11/25\n",
      "55000/55000 [==============================] - 5s 97us/sample - loss: 0.2984 - accuracy: 0.9010 - val_loss: 0.5083 - val_accuracy: 0.8610\n",
      "Epoch 12/25\n",
      "55000/55000 [==============================] - 5s 99us/sample - loss: 0.2736 - accuracy: 0.9080 - val_loss: 0.4497 - val_accuracy: 0.8826\n",
      "Epoch 13/25\n",
      "55000/55000 [==============================] - 5s 99us/sample - loss: 0.2603 - accuracy: 0.9128 - val_loss: 0.4366 - val_accuracy: 0.8808\n",
      "Epoch 14/25\n",
      "55000/55000 [==============================] - 5s 100us/sample - loss: 0.2382 - accuracy: 0.9197 - val_loss: 0.4692 - val_accuracy: 0.8828\n",
      "Epoch 15/25\n",
      "55000/55000 [==============================] - 6s 102us/sample - loss: 0.2240 - accuracy: 0.9252 - val_loss: 0.4609 - val_accuracy: 0.8774\n",
      "Epoch 16/25\n",
      "55000/55000 [==============================] - 5s 99us/sample - loss: 0.2020 - accuracy: 0.9306 - val_loss: 0.4950 - val_accuracy: 0.8808\n",
      "Epoch 17/25\n",
      "55000/55000 [==============================] - 5s 100us/sample - loss: 0.1950 - accuracy: 0.9340 - val_loss: 0.4985 - val_accuracy: 0.8856\n",
      "Epoch 18/25\n",
      "55000/55000 [==============================] - 6s 102us/sample - loss: 0.1785 - accuracy: 0.9388 - val_loss: 0.5071 - val_accuracy: 0.8854\n",
      "Epoch 19/25\n",
      "55000/55000 [==============================] - 5s 100us/sample - loss: 0.1649 - accuracy: 0.9447 - val_loss: 0.4798 - val_accuracy: 0.8890\n",
      "Epoch 20/25\n",
      "55000/55000 [==============================] - 5s 100us/sample - loss: 0.1561 - accuracy: 0.9471 - val_loss: 0.5023 - val_accuracy: 0.8896\n",
      "Epoch 21/25\n",
      "55000/55000 [==============================] - 5s 98us/sample - loss: 0.1442 - accuracy: 0.9520 - val_loss: 0.5253 - val_accuracy: 0.8952\n",
      "Epoch 22/25\n",
      "55000/55000 [==============================] - 5s 99us/sample - loss: 0.1369 - accuracy: 0.9540 - val_loss: 0.5558 - val_accuracy: 0.8922\n",
      "Epoch 23/25\n",
      "55000/55000 [==============================] - 5s 98us/sample - loss: 0.1277 - accuracy: 0.9576 - val_loss: 0.5786 - val_accuracy: 0.8908\n",
      "Epoch 24/25\n",
      "55000/55000 [==============================] - 5s 99us/sample - loss: 0.1204 - accuracy: 0.9611 - val_loss: 0.5991 - val_accuracy: 0.8902\n",
      "Epoch 25/25\n",
      "55000/55000 [==============================] - 6s 102us/sample - loss: 0.1130 - accuracy: 0.9638 - val_loss: 0.5984 - val_accuracy: 0.8894\n"
     ]
    }
   ],
   "source": [
    "lr_scheduler = keras.callbacks.LearningRateScheduler(exponential_decay_fn)\n",
    "history = model.fit(X_train_scaled, y_train, epochs=n_epochs,\n",
    "                    validation_data=(X_valid_scaled, y_valid),\n",
    "                    callbacks=[lr_scheduler])"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 82,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "image/png": "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\n",
      "text/plain": [
       "<Figure size 432x288 with 1 Axes>"
      ]
     },
     "metadata": {
      "needs_background": "light"
     },
     "output_type": "display_data"
    }
   ],
   "source": [
    "plt.plot(history.epoch, history.history[\"lr\"], \"o-\")\n",
    "plt.axis([0, n_epochs - 1, 0, 0.011])\n",
    "plt.xlabel(\"Epoch\")\n",
    "plt.ylabel(\"Learning Rate\")\n",
    "plt.title(\"Exponential Scheduling\", fontsize=14)\n",
    "plt.grid(True)\n",
    "plt.show()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "The schedule function can take the current learning rate as a second argument:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 83,
   "metadata": {},
   "outputs": [],
   "source": [
    "def exponential_decay_fn(epoch, lr):\n",
    "    return lr * 0.1**(1 / 20)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "If you want to update the learning rate at each iteration rather than at each epoch, you must write your own callback class:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 84,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Train on 55000 samples, validate on 5000 samples\n",
      "Epoch 1/25\n",
      "55000/55000 [==============================] - 7s 132us/sample - loss: 0.8067 - accuracy: 0.7678 - val_loss: 0.7942 - val_accuracy: 0.7780\n",
      "Epoch 2/25\n",
      "55000/55000 [==============================] - 7s 122us/sample - loss: 0.6784 - accuracy: 0.7937 - val_loss: 0.8375 - val_accuracy: 0.8120\n",
      "Epoch 3/25\n",
      "55000/55000 [==============================] - 6s 114us/sample - loss: 0.6060 - accuracy: 0.8148 - val_loss: 0.6303 - val_accuracy: 0.8304\n",
      "Epoch 4/25\n",
      "55000/55000 [==============================] - 6s 114us/sample - loss: 0.5279 - accuracy: 0.8341 - val_loss: 0.5724 - val_accuracy: 0.8196\n",
      "Epoch 5/25\n",
      "55000/55000 [==============================] - 6s 112us/sample - loss: 0.4803 - accuracy: 0.8486 - val_loss: 0.5488 - val_accuracy: 0.8486\n",
      "Epoch 6/25\n",
      "55000/55000 [==============================] - 6s 113us/sample - loss: 0.4305 - accuracy: 0.8611 - val_loss: 0.4778 - val_accuracy: 0.8470\n",
      "Epoch 7/25\n",
      "55000/55000 [==============================] - 6s 112us/sample - loss: 0.3969 - accuracy: 0.8699 - val_loss: 0.4922 - val_accuracy: 0.8584\n",
      "Epoch 8/25\n",
      "55000/55000 [==============================] - 6s 111us/sample - loss: 0.3799 - accuracy: 0.8777 - val_loss: 0.5417 - val_accuracy: 0.8614\n",
      "Epoch 9/25\n",
      "55000/55000 [==============================] - 6s 111us/sample - loss: 0.3475 - accuracy: 0.8851 - val_loss: 0.5032 - val_accuracy: 0.8734\n",
      "Epoch 10/25\n",
      "55000/55000 [==============================] - 6s 110us/sample - loss: 0.3256 - accuracy: 0.8937 - val_loss: 0.4433 - val_accuracy: 0.8802\n",
      "Epoch 11/25\n",
      "55000/55000 [==============================] - 6s 110us/sample - loss: 0.2944 - accuracy: 0.9017 - val_loss: 0.4888 - val_accuracy: 0.8742\n",
      "Epoch 12/25\n",
      "55000/55000 [==============================] - 6s 110us/sample - loss: 0.2767 - accuracy: 0.9077 - val_loss: 0.4626 - val_accuracy: 0.8706\n",
      "Epoch 13/25\n",
      "55000/55000 [==============================] - 6s 111us/sample - loss: 0.2572 - accuracy: 0.9134 - val_loss: 0.4750 - val_accuracy: 0.8770\n",
      "Epoch 14/25\n",
      "55000/55000 [==============================] - 6s 111us/sample - loss: 0.2391 - accuracy: 0.9185 - val_loss: 0.4633 - val_accuracy: 0.8900\n",
      "Epoch 15/25\n",
      "55000/55000 [==============================] - 6s 112us/sample - loss: 0.2180 - accuracy: 0.9251 - val_loss: 0.4573 - val_accuracy: 0.8768\n",
      "Epoch 16/25\n",
      "55000/55000 [==============================] - 6s 110us/sample - loss: 0.2029 - accuracy: 0.9311 - val_loss: 0.4748 - val_accuracy: 0.8840\n",
      "Epoch 17/25\n",
      "55000/55000 [==============================] - 6s 112us/sample - loss: 0.1884 - accuracy: 0.9357 - val_loss: 0.5171 - val_accuracy: 0.8840\n",
      "Epoch 18/25\n",
      "55000/55000 [==============================] - 6s 111us/sample - loss: 0.1813 - accuracy: 0.9382 - val_loss: 0.5293 - val_accuracy: 0.8822\n",
      "Epoch 19/25\n",
      "55000/55000 [==============================] - 6s 112us/sample - loss: 0.1618 - accuracy: 0.9445 - val_loss: 0.5328 - val_accuracy: 0.8872\n",
      "Epoch 20/25\n",
      "55000/55000 [==============================] - 6s 111us/sample - loss: 0.1570 - accuracy: 0.9483 - val_loss: 0.5453 - val_accuracy: 0.8870\n",
      "Epoch 21/25\n",
      "55000/55000 [==============================] - 6s 112us/sample - loss: 0.1422 - accuracy: 0.9523 - val_loss: 0.5596 - val_accuracy: 0.8892\n",
      "Epoch 22/25\n",
      "55000/55000 [==============================] - 6s 111us/sample - loss: 0.1329 - accuracy: 0.9563 - val_loss: 0.5717 - val_accuracy: 0.8894\n",
      "Epoch 23/25\n",
      "55000/55000 [==============================] - 6s 110us/sample - loss: 0.1248 - accuracy: 0.9592 - val_loss: 0.5959 - val_accuracy: 0.8930\n",
      "Epoch 24/25\n",
      "55000/55000 [==============================] - 6s 112us/sample - loss: 0.1178 - accuracy: 0.9606 - val_loss: 0.5875 - val_accuracy: 0.8896\n",
      "Epoch 25/25\n",
      "55000/55000 [==============================] - 6s 111us/sample - loss: 0.1103 - accuracy: 0.9646 - val_loss: 0.6103 - val_accuracy: 0.8904\n"
     ]
    }
   ],
   "source": [
    "K = keras.backend\n",
    "\n",
    "class ExponentialDecay(keras.callbacks.Callback):\n",
    "    def __init__(self, s=40000):\n",
    "        super().__init__()\n",
    "        self.s = s\n",
    "\n",
    "    def on_batch_begin(self, batch, logs=None):\n",
    "        # Note: the `batch` argument is reset at each epoch\n",
    "        lr = K.get_value(self.model.optimizer.lr)\n",
    "        K.set_value(self.model.optimizer.lr, lr * 0.1**(1 / s))\n",
    "\n",
    "    def on_epoch_end(self, epoch, logs=None):\n",
    "        logs = logs or {}\n",
    "        logs['lr'] = K.get_value(self.model.optimizer.lr)\n",
    "\n",
    "model = keras.models.Sequential([\n",
    "    keras.layers.Flatten(input_shape=[28, 28]),\n",
    "    keras.layers.Dense(300, activation=\"selu\", kernel_initializer=\"lecun_normal\"),\n",
    "    keras.layers.Dense(100, activation=\"selu\", kernel_initializer=\"lecun_normal\"),\n",
    "    keras.layers.Dense(10, activation=\"softmax\")\n",
    "])\n",
    "lr0 = 0.01\n",
    "optimizer = keras.optimizers.Nadam(lr=lr0)\n",
    "model.compile(loss=\"sparse_categorical_crossentropy\", optimizer=optimizer, metrics=[\"accuracy\"])\n",
    "n_epochs = 25\n",
    "\n",
    "s = 20 * len(X_train) // 32 # number of steps in 20 epochs (batch size = 32)\n",
    "exp_decay = ExponentialDecay(s)\n",
    "history = model.fit(X_train_scaled, y_train, epochs=n_epochs,\n",
    "                    validation_data=(X_valid_scaled, y_valid),\n",
    "                    callbacks=[exp_decay])"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 85,
   "metadata": {},
   "outputs": [],
   "source": [
    "n_steps = n_epochs * len(X_train) // 32\n",
    "steps = np.arange(n_steps)\n",
    "lrs = lr0 * 0.1**(steps / s)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 86,
   "metadata": {
    "scrolled": true
   },
   "outputs": [
    {
     "data": {
      "image/png": "iVBORw0KGgoAAAANSUhEUgAAAZsAAAEeCAYAAABc5biTAAAABHNCSVQICAgIfAhkiAAAAAlwSFlzAAALEgAACxIB0t1+/AAAADl0RVh0U29mdHdhcmUAbWF0cGxvdGxpYiB2ZXJzaW9uIDMuMC4yLCBodHRwOi8vbWF0cGxvdGxpYi5vcmcvOIA7rQAAIABJREFUeJzs3Xd8FHX6wPHPk94TktA70ntTVEDAhnp6Yj/xVEQP6/kTRU89PbseKtZTxHJiPcWCKOqhHgQEFQGRXqSH0BNISEgIhOf3x0xwWTbJJmSzKc/79ZpXduf7nZlnJrvz7Mx85zuiqhhjjDGBFBLsAIwxxtR+lmyMMcYEnCUbY4wxAWfJxhhjTMBZsjHGGBNwlmyMMcYEnCUbU+OJyAgRyS3nNGki8q9AxeQuY4OIjAnAfC8WkXLds+C9jSqyzY6FiDwgIv+uquX5WL6KyMVBWG6Z21lEbhaRL6oqpmCxZFODichE90vkPfwU7NgCpYSdxodAmwAs6zoRWSgiuSKSLSKLReTRyl5OkARkm/kiIg2AO4Aave1E5EERWRqAWb8O9BGRgQGYd7URFuwAzDH7DrjSa1xhMAIJFlXNB/Irc54iMhJ4ARgN/A8IB7oCJ1XmcoIlENusFNcBP6vqukAvSETCVfVAoJdTmVR1v4i8D9wKfB/seALFjmxqvv2qus1ryAIQkUEickBEBhdXFpHrRSRHRNq479NE5BUReV5EdrvDUyIS4jFNPRF5yy3LF5HvRKSLR/kI99f/aSKyVETyRGSGiLT2DFREzhORBSJSICLrReQxEYnwKN8gIveJyAQ3xs0icqdnufvyI/cIZ4Pn8j3qHSciU0RkmxvLLyJybjm36x+BT1V1gqquUdUVqvqRqt7utU7niMhcd7tkisgXIhLlUSWqpPVxp08UkVdFZIeI7BWRmSLS16vOVSKyUUT2ichUoKFX+VG/uMs6feNjmz3o/u/+JCJr3Vg+E5FUjzphIvKsx+fkWREZLyJpZWzL4cARp4n8/NxFiMhYd7vtE5F5IjLUo3yw+zk4R0R+FpFCYCglayQiX7rz2igif/aK6Z8issr9X24QkSeL/5ciMgJ4AOgiv59BGOGWJbrbYav72V4hIpd5zbvU7wbwOfBHEYkpY1vWXKpqQw0dgInA1DLqPA6kA/WAjkAecLVHeRqwF3jRLb8UyAZu96gzBVgJnAJ0w/lipAPRbvkI4ADOUdYJQHdgITDNYx5DgRzgGuA4YAiwCnjao84GIBO4BWgL/BVQ4CS3vL77/jqgEVDfY/m5HvPpAdzgxtoW+DvO0V5Hr/X+Vynb7RVgNdCmlDpnAQdxTg91dtd7DBDj5/oIMBv40t1ubYFH3O3U2K3TDzjkrkN74Hp3nuoRx4PAUq/YvLdJWe8fBHKBye56nARsBCZ41Lkb2A1cBHQAnnc/K2mlbKNkN/7+XuPTKPtz9x7wE87nro27HQuBHm75YHd7LgHOdOvULyEOdbfb9e52/LsbV1+POvcD/YFWwDnAJuARtywaeBrne9DIHaLd/+EcYLn7eWgDnA1c4O93w60XAxQBpwV7vxKoIegB2HAM/zwn2Rx0dxKew1iPOuHAPOBT4BfgQ695pOHsVMVj3H3AZvd1O/eLeopHeaK7Y7jOfT/CrdPBo84VwP7i+QKzgPu9lj3Mjbe4zgbgP151fgPu83ivwMVedUbgseMsYVv95DWfNEpPNo2BH93l/Qa8C1wFhHvUmQN8UMo8Sl0f4FR3/aO96vwK3OW+fh/41qv8dQKTbAqARI9xfwfWeLzfCtzt8V5wfjCklbINerrbsHU5P3fH4SSDFl7TfQa87L4e7M77Ij++Kwq85jXuO+DdUqa5wWv9fW3nM9w4O5UwjxGU8d3wGJ8FXFvWutTUwU6j1XyzcL7QnsNTxYXqnL8eDpwLNMD5ZeftJ3U/7a4fgaYikgB0wvky/egxz2ycX5OdPabZr6qrPN5vASJwjqgA+gB/d0+35bqncN4HYnF+JRZb7BXbFjduv4lIrHsKZLl7eiYX6Au08HceqrpVVU/COTp6DmfHOgH42eNURy+c6zmlKW19+uD8ot3ptV264uxswdn+P3rNw/t9Zdno/m+PilVEEnH+Tz8XF7qfmZ8pXbT7t8BHWWmfu94423y517b5A79vm2Lzy4jBc/7e7w9/hsVp5TfbPf2aCzxL2Z+ZXsBWVV1RSp2yvhvF8vl9e9U61kCg5tunqmvKqHMizvW5JJxTUXsqadmeO4qDJZSFePx9CPjIx3x2erz2vrirlP/a4tM4pzTG4BxJ7APexvmCl4uqLgWWAi+JyACcC7iX4hxV+qO09QkBtgO+WiHllCPMQzg7Zk/h5Zi+WGVse2+73L/1cI6M/BXiLv94H3F5N2zIq1hovxORE4EPcD6jo3G+I3/E+Swdq7K+G8WSOfK7UKvYkU0t516I/BdwM/At8K6IeP/I6CcinjurE4EtqpoDrMD5nBxuheX+8uyGc57aX7/gXDNZ42Pw/jKW5gAQWkadAcDbqvqJqi4GNnP0r+GKKF7fOPfvQuC0Y5jfLzgX+w/52CY73DorcP4fnrzf7wQaev0Pex5DXEdxj3i24ez8AXCXd3yJEznW4iTOzj7KSvvcLcRJoI18bJuMCq6Gr+1YfETSH8hQ1UdUdZ6q/ga09KpfyNGfvYVAYxHpVMGYAKdRCxCF85molezIpuaLFJFGXuOKVHWniIQC7wAzVXWCiHyMc/rrAZyLocWaAM+JyMs4SeRO3HsiVPU3EZkCTBCRUTi/+B7D2YG8X444HwamishGYBLOr72uwAmqelc55rMBOE1EZuKcntjto85q4AI37gM46xvlo16JRGQ8zumO6TjJqjHONYV9wDdutceAL0RkDc62EJwL1RNUdZ8fi/kO57rPFBG5i98vPp8FfKeq3+M0v/5BRO4BPsa5TnGB13zScH4V3ysiH7h1AnED4/PAXSKyGifxXo+zXUo8YlHVQyLyHc4PgI+9ikv73K0WkfeAiSJyB85OOBln3dap6qcViP9CEZmHs70uxvmh0M8tW41zCu8KnNNrQ4HLvabfALQUkd44jQf24pxGnQt8IiKj3fm0BWJV9bNyxDbQXa/fKrBeNYId2dR8p+N82T2HhW7ZvTgf/GsBVDUTuBq42z0lVOw9nF9sc4HXgDdwzlcXuwbn3Pzn7t8Y4Cx17tXwi6pOwznfPsSdx884rZs2+b+qgHNz4BCc1nALS6hzO7AD55TX1ziNA8p7/8K3ODuiSTg7kMnu+DNUdTWAqn6Fs+M/241lphvbIX8W4F6vOAcnob2Gc7F9Ek5Lry1unZ9w/n834lz/uRDnQrXnfFa45aPcOmfgtEKsbE/j/Hh5E2ebgrNdfF2P8fQqcJn748eTP5+7N4EncRLxVJyWaRsrGP+DOC3pFuNsr2tUdR6Aqn6Bc63zOX7fhv/wmv4T4CucBLMTuFxVD+H8/+fgNCJZgZOUy3vK9nKcbVBrFbcCMnWUe4/EUlW9JdixmJpHRBYCs1X1r2XU+xGnFdk77vs07HMHgIh0xUlg7b0aaNQqdhrNGOMXEWmJc3ppJk4DhL/g3DfyFz8mvx6n5ZY5WhPgqtqcaMCSjTHGf4dw7jV6CucU/HLgbFUts+mx21DDuxm4AVT1m7Jr1Xx2Gs0YY0zAWQMBY4wxAWen0VxJSUnatm3bYIfhU15eHrGxscEOwyeLrWIstoqx2ComkLEtWLBgl6rWL7NisPvLqS5D+/bttbqaMWNGsEMokcVWMRZbxVhsFRPI2ID5an2jGWOMqQ4s2RhjjAk4SzbGGGMCzpKNMcaYgLNkY4wxJuAs2RhjjAk4SzbGGGMCzpKNMcaYgLNkY4wxJuAs2RhjjAk4SzbGGGMCzpKNMcaYgLNkY4wxJuAs2RhjjAk4SzbGGGMCrkqTjYgki8hkEckTkY0iMryEeiIiY0Uk0x3Gioh4lL8qIqtE5JCIjPAx/WgR2SYiOSLybxGJDOBqGWOMKUNVH9m8BBQCDYErgPEi0sVHvVHAMKAH0B04D7jeo3wRcBPwi/eEIjIUuBs4DWgJtAEeKiuwQ1qe1TDGGFMeVZZsRCQWuAi4X1VzVXU28DlwpY/qVwPjVHWzqmYA44ARxYWq+pKq/g8oKGHaN1R1maruBh7xnLYkm3MPsWdfYTnXyhhjjD/EeapnFSxIpBcwR1VjPMaNAQap6nledbOBM1V1rvu+LzBDVeO96s0GXlfViR7jFgGPq+qH7vtUYCeQqqqZXtOPwjmKIqJR2z7D7/sXV3epfmfccnNziYuLC3YYPllsFWOxVYzFVjGBjG3IkCELVLVvWfXCArJ03+KAHK9x2UB8CXWzverFiYho2dnR17S4yzki2ajqq8CrAJGN22na5oOMPr8f3ZsllbGIqpWWlsbgwYODHYZPFlvFWGwVY7FVTHWIrSqv2eQCCV7jEoC9ftRNAHL9SDQlTUsJy/m9UoSgCvd/tpRDdgHHGGMqVVUmm9VAmIi08xjXA1jmo+4yt6yser74mna79yk0b0mRQqOEKBZtzuaDeel+LsoYY4w/qizZqGoe8CnwsIjEikh/4HzgHR/V3wZuF5GmItIEuAOYWFwoIhEiEgUIEC4iUSIS4jHttSLSWUSSgPs8py1JiMB953YC4MlpK8nKs8YCxhhTWaq66fNNQDSwA/gPcKOqLhORgSKS61FvAvAFsARYCnzpjiv2DZAPnIxzzSUfOAVAVf8LPAnMADYBG4EH/AnuD90aM6BtKnv2HeCpaSsrvJLGGGOOVKXJRlWzVHWYqsaqagtVfd8d/72qxnnUU1W9S1WT3eEuz+s1qjpYVcVrSPMof0ZVG6pqgqpeo6r7/YlPRHjwj10IDxU+mJfOwk27K3HtjTGm7rLuary0bRDHdQPbOI0FpiylyBoLGGPMMbNk48NfT21Lk8Qolmbk8P7cjcEOxxhjajxLNj7ERITxj/M6A/Dkf1exPcdXRwXGGGP8ZcmmBEO7NOK0jg3Yu/8gD33hb6trY4wxvliyKYGI8PCwrsREhPLVkm18t3x7sEMyxpgay5JNKZomRTPmzA4A/GPKUnL3HwxyRMYYUzNZsinD1Se3onuzRLZkFzDum1XBDscYY2okSzZlCA0RnriwG6EhwsQfNvBr+p5gh2SMMTWOJRs/dGmSyHUDWqMK93y6hANFh4IdkjHG1CiWbPz0f6e3o3lyNCu25vDG7PXBDscYY2oUSzZ+iokI49Fh3QB47rvVbMzMC3JExhhTc1iyKYdB7eszrGcTCg4c4q6PF9tzb4wxxk+WbMrpH+d1ITUugrnrs3jXurIxxhi/WLIpp+TYCB4d1hWAf369kvSsfUGOyBhjqj9LNhVwVtfGnNu9MfsKi+x0mjHG+MGSTQU99McupMRG8OO6TN7/eVOwwzHGmGrNkk0FpcRF8vD5zum0J75awebddjrNGGNKYsnmGPyhe2PO6daIvMIi7v5kCR4PEzXGGOPBks0xevj8rtSLCWf2ml18MC892OEYY0y1ZMnmGKXGRfKQezrt0anLrXWaMcb4YMmmEpzncTrt9km/UmSt04wx5giWbCqBiPDYsG40iI9k3obdvDprXbBDMsaYasWSTSWpFxvBU5f0AOCZb1exbEt2kCMyxpjqw5JNJRrUvj5XndSSA0XK6A9/peBAUbBDMsaYasGSTSW75+xOtEmNZfX2XJ6aZk/2NMYYsGRT6aIjQnn2sp6EhghvzF7PnDW7gh2SMcYEnSWbAOjRPIlbT20HwJiPFpGdfyDIERljTHBZsgmQm4ccR4/mSWzNLuDeyda7gDGmbrNkEyBhoSE8f1lPYiNC+XLxVibNt94FjDF1lyWbAGqVGsujFzi9Czzw+TLW7Ngb5IiMMSY4qjTZiEiyiEwWkTwR2Sgiw0uoJyIyVkQy3WGsiIhHeU8RWSAi+9y/PT3KIkXkFRHZLiJZIvKFiDStivXz5YJezbiwV1MKDhzilvcXWnNoY0ydVNVHNi8BhUBD4ApgvIh08VFvFDAM6AF0B84DrgcQkQhgCvAuUA94C5jijgf4P+Akd7omwG7gxQCtj18eHtaVVikxrNy2l8e/WhHMUIwxJiiqLNmISCxwEXC/quaq6mzgc+BKH9WvBsap6mZVzQDGASPcssFAGPCcqu5X1RcAAU51y1sD01R1u6oWAB8CvhJalYmLDOPFy3sTHiq8/eNGpi3bFsxwjDGmyklVtZISkV7AHFWN8Rg3Bhikqud51c0GzlTVue77vsAMVY0XkdFu2dke9ae65ePcus8DlwB7gNeBHap6m4+YRuEcRVG/fv0+kyZNqtyV9jJtwwH+s7KQ2HB4+ORoUqL9y/W5ubnExcUFNLaKstgqxmKrGIutYgIZ25AhQxaoat+y6oUFZOm+xQE5XuOygfgS6mZ71Ytzr9t4l3nP5zcgHcgAioAlwC2+AlLVV4FXATp06KCDBw/2c1UqZpAq2ybOY8aqnXy4MZr3/9KPsNCyE05aWhqBjq2iLLaKsdgqxmKrmOoQm9+n0USkoYiMEZHxIpLqjusvIq39nEUukOA1LgHw1UTLu24CkKvOYVhZ83kJiARSgFjgU+BrP2MMKBHh6Ut60CA+kp83ZPHMt6uDHZIxxlQJv5KNiPQBVuFc1L+W33f2ZwCP+bms1UCYiLTzGNcDWOaj7jK3zFe9ZUB3z9ZpOI0Bist7AhNVNUtV9+M0DjihOEEGW0pcJC9c3osQgZfT1vLd8u3BDskYYwLO3yObp4HnVbUXsN9j/DSgvz8zUNU8nKOMh0UkVkT6A+cD7/io/jZwu4g0FZEmwB3ARLcsDef02K1uM+fiU2TT3b/zgKtEJFFEwoGbgC2qWm06KTuxTQp3Du0IwO2TfrWnexpjaj1/k00fnCbG3rbiNGP2101ANLAD+A9wo6ouE5GBIpLrUW8C8AXO9ZalwJfuOFS1EKdZ9FU4DQBGAsPc8QBjgAKcazc7gXOAC8oRY5W4/pQ2nN6pITkFB7nxvQV2/40xplbzt4FAPs49Ld464iQOv6hqFk6i8B7/Pc6F/+L3CtzlDr7msxAnAfoqy8Q53VethYQI4y7pwbn/+p6lGTk8PHU5j1/QLdhhGWNMQPh7ZDMFeEBEIt33KiKtgLHAJwGIq05IjAln/BV9iAgL4f25m/j0l83BDskYYwLC32QzBkjGOS0VA8wG1uCcxrovMKHVDV2bJvLQH517Tu+dvIRV26z/NGNM7eNXslHVHFUdgHMK7G84N02epaqD3Av/5hj86fjmXNjb6T/thncX2PNvjDG1jr9Nn68SkUhVna6qT6vqk6r6nYhEiMhVgQ6ythMRHhvWjY6N4lm/K4/bPlhI0SF7/o0xpvbw9zTam0Cij/Hxbpk5RtERobx2VV+SYsKZsWonz9oNn8aYWsTfZCOAr5/aLTi66xhTQc2TY3hpeG9CBP41Yw1fLdka7JCMMaZSlNr0WUSW4CQZBWaKyEGP4lCgJfBV4MKre/q3TeXeczrx6JcruGPSIlqnxgY7JGOMOWZl3Wfzsfu3K86NlZ43XhYCG7Cmz5Xu2gGtWbYlh8kLMxj1znz+1lPKnsgYY6qxUpONqj4EICIbgA/d58OYABMRnriwG2t25LIkI5vxi0I467RDfvUQbYwx1ZG/TZ/fskRTtaLCQ5lwZR9SYiNYlnmIx79aGeyQjDGmwvxt+hwhIg+JyGoRKRCRIs8h0EHWVU2Sohn/5z6ECvx7znrem7sx2CEZY0yF+Hte5hHcRzUDh4A7cZ4bk4nTuaYJkBNaJzOiSwQA/5iyjNm/VZvOq40xxm/+JptLgRtUdQJO9/5TVPVW4AGcZ9qYABrYLJwbBh1H0SHlxvcWsGaHdWljjKlZ/E02DYHl7utcIMl9/V/gzMoOyhztrqEdOKtLI/YWHGTkxPlk5RWWPZExxlQT/iabTUAT9/UaYKj7+iScxw+YAAsJEZ65rAfdmiayKWsf178zn/0H7XKZMaZm8DfZTAZOc18/DzwkIutxnp75egDiMj7ERITx+tV9aZQQxbwNu7n7kyU4j/4xxpjqza+Hp6nqPR6vPxaRdJzHQa9W1amBCs4crWFCFK9f3ZdLXvmRyQszaJkSw22ntw92WMYYU6oK3SWoqnNV9RlVnSoi1p9KFevaNJEXLu9FiMBz3/3Gh/M2BTskY4wpVYVvSReRKBG5E1hfifEYP53RuSEPn98VgHsnL2X6yu1BjsgYY0pWarJxb+Z8TETmicgPIjLMHX8VsA64DXi2CuI0Pvz5xJbcMqQtRYeUm99byK/pe4IdkjHG+FTWkc2DwC3ARqA18JGIvAz8HbgHaKWqTwQ0QlOqO85sz8V9mpF/oIiRE+exYZc9ONUYU/2UlWwuBUao6sXAWTiPFagHdHH7S7PnFwdZcaedg9rXJyuvkKv+/TM79+4PdljGGHOEspJNc2AegKouwnmswFhVPVjqVKZKhYeG8PIVvQ/fgzNy4jzy9tu/yBhTfZSVbMIBz5/JB7Anc1ZLsZFh/HvE8bRIjmFJRjaj3plPwQG76dMYUz34c5/NEyKyz30dATwoIkckHLefNBNk9eMjeXvkCVz8yo/MWZPJrf9ZyMtX9Lbn4Bhjgq6svdAs4Digmzv8ALTweN8N5ymepppolRrLu9edQGJ0ON8s385dHy/m0CHrZcAYE1xlPalzcBXFYSpRx0YJvHnN8fz59bl8ujCD+KgwHvxjF0Ts8dLGmOCw8yu1VO8W9Xjtqr5EhIbw1o8beebb1cEOyRhTh1myqcX6t03lxeG9CA0RXpy+hldnrQ12SMaYOqpKk42IJIvIZBHJE5GNIjK8hHoiImNFJNMdxorHOSAR6SkiC0Rkn/u3p9f0vUVklojkish2Efm/QK9bdTW0SyOeurg7AI9/tZJ3frJHSxtjql5VH9m8hHOvTkPgCmC8iHTxUW8UMAzoAXQHzgOuB6cLHWAK8C7ODaZvAVPc8YhIKs5D3SYAKUBb4JvArVL1d2HvZjx8vrOZ7/9sKe/PtY47jTFVq8qSjds79EXA/aqaq6qzgc+BK31UvxoYp6qbVTUDGAeMcMsG4zRseE5V96vqC4AAp7rltwPTVPU9t3yvqq4I2IrVEFed1Ir7z+0MwL2TlzBpXnqQIzLG1CXiz8O3RKRFCUUKFKjqTj/m0QuYo6oxHuPGAINU9TyvutnAmao6133fF5ihqvEiMtotO9uj/lS3fJyITAeWAMfjHNXMBW5W1aN+zovIKJyjKOrXr99n0qRJZa1GUOTm5hIXF1cp8/p6/QE+XFWIANd2i2BA0/BqE1tls9gqxmKrmLoa25AhQxaoat8yK6pqmQNwCCgqZdgNPAOElTKPgcA2r3F/AdJ81C0COnq8b4eT2AS4H/jAq/57wIPu69XAHpxkEwW8gJPkSl3H9u3ba3U1Y8aMSp3f+LQ12vJvU7XV3VP1kwXpxzSvyo6tMllsFWOxVUxdjQ2Yr37kEb+e1AlcDjwJvIJzpADQD+eo4EEgCbgP2As8UMI8coEEr3EJ7jRl1U0AclVVRaSs+eQDk1V1HoCIPATsEpFEVbWudoAbBh1H0SHlqWmrGPPRIkJDhPN7Ng12WMaYWszfZHMjMFpVP/UYN11EVgH/p6qDRGQH8BAlJ5vVQJiItFPV39xxPYBlPuouc8t+9lFvGXCHiIibVcFpRPCS+3oxzlFQMbt93oeb3efgPPPtakZ/+CuqMKyXJRxjTGD420CgH851EG9LcU5XAfwINCtpBqqaB3wKPCwisSLSHzgfeMdH9beB20WkqYg0Ae4AJrplaTin2W4VkUgRucUdP939+yZwgds8OhzntNtsO6o52q2nteO209txSGH0pF/t8dLGmIDxN9lsxL2Q7uUvQPEeqj6QVcZ8bgKigR3Af4AbVXWZiAx0T48VmwB8gZPglgJfuuNQ1UKcZtFX4VybGQkMc8ejqtOBe91pduA0EvB5P4+B205vz51DO6AKf/tkCRPn2FO+jTGVz9/TaHcAn4jIObjPtwH64nTSeZH7/nig1OZcqpqFkyi8x38PxHm8V+Aud/A1n4VAn1KWMx4YX1os5nc3D2lLdHgoD09dzoNfLKfg4CFuGHRcsMMyxtQifiUbVf1SRNrhHJl0cEd/DryibpNiVX05MCGaqjByQGuiwkP5+2dL+OfXK8kvLOK209tZ553GmErh75ENqpoO3BPAWEyQDe/XgqjwEMZ8tIjn//cbBQeKuPvsjpZwjDHHzO9kIyIxQE+gAV7XerxaqZka7MLezYgMC+X/PljIhFnryN1/kIfP70poiCUcY0zF+ZVsROR0nAv6KT6KFQitzKBMcP2he2Miw0K46f1feG/uJnbvK+TZy3oSGWb/ZmNMxfjbGu15nNZdzVQ1xGuwPVAtdHrnhrwz8gTiI8P4ask2rnlzHnsLDgQ7LGNMDeVvsmkFPKKqWwIYi6lm+rVJ4cPrT6J+fCQ/rM3k8td+Yufe/cEOyxhTA/mbbObweys0U4d0bpLAJzecTKuUGJZm5HDJKz+QnrUv2GEZY2oYf5PNK8DTInKdiPRzH052eAhkgCb4WqTE8NENJ9OlSQIbMvdx4fgfWL4lJ9hhGWNqEH+TzcdAR+BVnG5p5nsM80qZztQS9eMj+WDUiZzUJoWde/dz6YQfmbm6zCdLGGMM4H+yaV3K0CYwoZnqJj4qnDevOZ5zuzcmd/9BRk6cR1q6NRowxpTN3x4E7MH1BoCo8FBe+FMvWiTH8HLaWiYuKyTq65XcNbQDIXYvjjGmBCUmGxG5EPhCVQ+4r0tkN3XWLSEhwl1ndaRFcgz3Tl7CKzPXkp61j3GX9iAq3FrCG2OOVtqRzcdAI5yekz8upZ7d1FlH/emEFuza9BsTlhzkyyVb2Zqdz2tX9SUlLjLYoRljqpkSr9m4N2zu8Hhd0mCJpg7rmhrKRzeeRJPEKH7ZtIcLXv6B1dt9PXzVGFOX+dtAwJgSdWyUwOSb+9O1aQKbsvZxwUtz+Hb59mCHZYypRsrTEWcz4BR8d8T5TCXHZWqYhglRfHT9ydz58SKmLt7KqHfmM+bMDtw0+DjrNdoY43dHnFcA/wYOAjtxrtMUU8CSjSE6IpQXL+9Fp8YxhMz6AAAgAElEQVQJPP3NKp6atooVW3N46uIeREfY2VZj6jJ/T6M9DIwDElS1laq29hjsPhtzmIhw85C2vHZlX2IjQpm6eCuXTPiBLXvygx2aMSaI/E02DYHXVbUokMGY2uP0zg2ZfHN/Wrp9qv3xX7OZuy4z2GEZY4LE32TzFdAvkIGY2qd9w3im3Nyf/m1T2JVbyPDX5/LarHWoatkTG2NqFX8bCHwLjBWRLsAS4Ig+SuymTlOSpJgI3rrmBJ7+ZjWvzFzLY1+tYMHG3Tx1SXfio8KDHZ4xpor4m2wmuH/v9VFmN3WaUoWFhnD32R3p1SKJMZMW8d9l21i1fS+v/LkPHRrFBzs8Y0wV8Os0mt3UaSrD0C6N+PyvA+jYKJ71u/IY9tIcPluYEeywjDFVoMxkIyLhIjJXROzhaeaYtU6NZfJN/bmwd1PyDxRx24e/ct9nSyg4YG1PjKnNykw2qnoA51ECdlXXVIroiFDGXdKDxy/oRkRoCO/+tIlhL81hzQ7r5saY2srf1mhvAX8JZCCmbhERhvdrwac3nUzr1FhWbtvLeS/OYdK8dGutZkwt5G8DgVjgChE5A1gA5HkWquqtlR2YqRu6Nk3ki78O4B+fLeXThRnc9clivl+zi8cu6EqCtVYzptbw98imE/ALsBvnyZzdPIaugQnN1BVxkWE8c1lPnrm0BzERoXyxaAt/eOF7fk3fE+zQjDGVxN8ndQ4JdCDGXNi7Gb1a1OOv//mFpRk5XDz+B0af0Z4bBh1HqD0F1JgazR4xYKqV1qmxfHLjyYzs35qDh5Snpq3i0gk/sjEzr+yJjTHVlt/JRkSGiMirIvJfEZnuOZRjHskiMllE8kRko4gML6GeiMhYEcl0h7Hi0U+9iPQUkQUiss/929PHPCJEZIWIbPY3PlM9RIaF8o/zOvP2yBNomBDJgo27Ofv573l/7iZrPGBMDeVXshGREcDXQDwwGOcxA/WA3sDycizvJaAQp2PPK4Dxbhc43kYBw4AeQHfgPOB6N5YIYArwrhvDW8AUd7ynO904TQ11Svv6TLvtFM7r0YR9hUXcO3kJIyfOY0dOQbBDM8aUk79HNmOAW1T1cpx+0e5R1V44O/xcf2YgIrHARcD9qpqrqrOBz4ErfVS/GhinqptVNQPn8QYj3LLBONeanlPV/ar6AiDAqR7Lag38GXjCz/Uz1VRSTAQvXt6LFy7vRWJ0ODNW7WToc7P4asnWYIdmjCkH8ee0hIjsAzqr6gYR2QWcqqqLRaQjkKaqjfyYRy9gjqrGeIwbAwxS1fO86mYDZ6rqXPd9X2CGqsaLyGi37GyP+lPd8nEe79/AaT33rqo2KyGmUThHUdSvX7/PpEmTytwWwZCbm0tcXFyww/CpKmPbXXCIN5YUsjTT6W3g+Eah/LlTJImRvhsP2HarGIutYupqbEOGDFmgqn3LqufvfTaZOKfQADJwmjsvBlKAaD/nEQfkeI3L9pivd91sr3px7nUb77Ij5iMiFwChqjpZRAaXFpCqvgq8CtChQwcdPLjU6kGTlpaGxeYYNlR596eNPPH1SuZtK+K3nAM8cF5nhvVsetTjp227VYzFVjEWW+n8PY32PXCm+3oS8IKIvAn8B+fxA/7IBRK8xiUAvvoo8a6bAOSqcxhW4nzcU3VPAnaTaS0lIlx5Uium3XYKA9ulsmffAUZ/uIiRE+fZ00CNqcb8TTa34CQWcK6DPIVzVDMJuM7PeawGwkSknce4HsAyH3WXuWW+6i0DusuRP2O7u+PbAa2A70VkG/Ap0FhEtolIKz/jNDVA8+QY3h55Ak9e3J2EqDBmrNrJmc/O4r25Gzl0yFqsGVPd+HtTZ5bH60PA2PIuSFXzRORT4GERuQ7oCZwPnOyj+tvA7SLyFU4HoHcAL7plaUARcKuIvMLvfbZNBw4BzT3mczLwL5xWc9YyrZYRES7t25zB7etz32dL+Wb5dv4+eSmf/7qFxy7oFuzwjDEeynOfTUMRGSMi40Uk1R3X32355a+bcK7x7MA5UrpRVZeJyEAR8WzVNgH4AuepoEuBL91xqGohTrPoq4A9wEhgmKoWqupBVd1WPABZwCH3vfVhX0s1SIhiwpV9eGl4b1LjIpi7Pouzn5/FJ6sL7dEFxlQT/t5n0wdYhXNvzLX8fs3kDOAxfxemqlmqOkxVY1W1haq+747/XlXjPOqpqt6lqsnucJd6NJtT1YWq2kdVo1W1t6ouLGF5aSW1RDO1i4jwh+6N+Xb0IC4/oTkHipQv1h3gjGdnMmPljmCHZ0yd5++RzdPA8+69Nfs9xk8D+ld6VMZUUL3YCJ64sDuf3HgSzeNDSM/K55qJ87jhnQXWgMCYIPI32fTBuVPf21ac3gCMqVb6tEzmwZOiuO8PnYiJCOW/y7Zx+jMzeW3WOgoPHgp2eMbUOf4mm3ycrmG8dcS5/mJMtRMaIlw3sA3/u2MQZ3dtxL7CIh77agVnPT+LGavsY2tMVfI32UwBHhCRSPe9uk2JxwKfBCAuYypN48Roxv+5D29eczxtUmNZtzOPa96cxzVv/szanX71tmSMOUbl6RstGaf5cAwwG1iDc+f+fYEJzZjKNaRDA/572ync94dOxEc69+YMfXYWj0xdTnb+gWCHZ0yt5leyUdUcVR2A0+T4b8DzwFmqeoqq2oNGTI0RERbCdQPbMOPOwVx+QnOKVHlj9nqGPJ3Ge3M3crDIrucYEwjleniaqk5X1adV9UlV/U5EWopI9ey90phSpMZF8sSF3fnilgGc0DqZrLxC/j55KWc//z3fLd9uz80xppId65M6k3AeG2BMjdS1aSIfjjqRfw3vRfPkaH7bkct1b8/nsgk/sWDj7mCHZ0ytYY+FNnWeiHBu9yZ8d/sg/nFuZ+rFhPPzhiwuGv8DN7yzwBoRGFMJLNkY44oMC2XkgNbMvGsINw85jqjwEP67bBtnPjuLeycvsSeEGnMMLNkY4yUhKpw7h3YkbcwQ/nR8c1SV9+duYuCTM3h06nJ25e4veybGmCOU2uuziHxexvTez5UxptZolBjFPy/qznUDW/PUtFVMW7ad12ev5725m7j65FZcf0ob6sVGBDtMY2qEsh4xkOlH+fpKisWYaqltg3gmXNmXpRnZPPvtav63cgevzFzLOz9uYOSA1lw3oA2JMeHBDtOYaq3UZKOq11RVIMZUd12bJvLGiOP5NX0Pz367mpmrd/Li9DVM/GED1w5ozTUnt7akY0wJ7JqNMeXUs3kSb408gU9uPIkBbVPZW3CQ5777jf5jp/PE1yvYsdcaEhjjzZKNMRXUp2Uy717Xjw9HncjAdqnk7j/IhJnrGDB2Bvd/tpT0rH3BDtGYasOvx0IbY0rWr00K/dqksCh9Dy+nrWHasu2889NG3v95E+f3bMKNg46jXcP4YIdpTFDZkY0xlaRH8yQmXNmXb0afwoW9mgLw6S8ZnPHsLEa9PZ95G7KsGxxTZ9mRjTGVrH3DeJ65rCejz2jPq7PW8eH8dL5Zvp1vlm+nR7NErh3YhphDlnRM3WJHNsYESPPkGB4Z1pXZfxvCrae2pV5MOIs2Z3PrfxZy16x8Xp211h5tYOoMSzbGBFiD+ChuP7MDP9x9Go9f0I029WPJKlAe/2olJz/xPx76YhmbMq0xgandLNkYU0WiI0IZ3q8F340exOg+kfRvm0JeYRFvztnAoKdnMHLiPGas3EGRnWIztZBdszGmioWECD3qh/F/l5zI8i05vDF7PV8s3sL0lTuYvnIHzZOj+XO/llzStznJ1h2OqSXsyMaYIOrcJIFxl/bgp3tO4+6zO9KsXjTpWfk88fVKTnzif9w+6Vd+Td9jrdhMjWdHNsZUA8mxEdww6Dj+MrANM1fv4J0fN5K2eief/pLBp79k0LVpApcd34I/9mhCYrR1iWNqHks2xlQjoSHCqR0bcmrHhmzMzOP9uZv4cH46SzNyWJqxlEenLuecbo25tG9zTmyTjIgEO2Rj/GLJxphqqmVKLPec04nRZ7Rn2rJtfDgvnR/WZjJ5YQaTF2bQMiWGS/s256LezWiUGBXscI0plSUbY6q5qPBQzu/ZlPN7NmVT5j4+WpDOR/M3szFzH09NW8W4b1YxuEMDLurdjNM6NSAqPDTYIRtzFEs2xtQgLVJiuOPMDtx2entm/baTSfPS+W7F9sMt2eIjwzinW2OG9WpKv9bJhITYaTZTPVRpazQRSRaRySKSJyIbRWR4CfVERMaKSKY7jBWPk9Mi0lNEFojIPvdvT4+yO0VkqYjsFZH1InJnVaybMVUpNEQY0qEB4//ch5/uOY37z+1Mt6aJ7N1/kA/np3P5az/Rf+x0/vn1SlZt2xvscI2p8iObl4BCoCHQE/hSRBap6jKveqOAYUAPQIFvcZ4I+oqIRABTgOeAl4HrgSki0k5VCwEBrgIWA8cB34hIuqp+EPC1MyYIUuIiuXZAa64d0Jo1O/by2cItTF6YQcaefF6ZuZZXZq6lU+MEhvVswh+6N6ZZvZhgh2zqoCo7shGRWOAi4H5VzVXV2cDnwJU+ql8NjFPVzaqaAYwDRrhlg3GS5HOqul9VX8BJMKcCqOqTqvqLqh5U1VU4ial/AFfNmGqjbYN4xgztwPd3DeGjG05ieL8WJEaHs2JrDk98vZIBY2dw/ktzeHXWWjbvti5yTNWRqrpZTER6AXNUNcZj3BhgkKqe51U3GzhTVee67/sCM1Q1XkRGu2Vne9Sf6paP85qPAL8AE1T1FR8xjcI5iqJ+/fp9Jk2aVElrW7lyc3OJi4sLdhg+WWwVU5WxHTikLN5ZxNytB/l1ZxGFRb+XtUkM4fhGYfRtGEr9mJAqj628LLaKCWRsQ4YMWaCqfcuqV5Wn0eKAHK9x2YCvp0rFuWWe9eLc5OFdVtp8HsQ5envTV0Cq+irwKkCHDh108ODBpa5AsKSlpWGxlZ/F9rsz3L/5hUWkrdrB1CVbmb5iB+uyi1iXXciHq6BHs0TO6daYRNnIubbdys1iK11VJptcIMFrXALg6+qld90EIFdVVUT8mo+I3IJz7Wagqu4/lsCNqS2iI0I5u1tjzu7W+KjEs2hzNos2O7/jXl81kzM6N+T0Tg3p1TzJWrWZY1aVyWY1EOZeyP/NHdcD8G4cgDuuB/Czj3rLgDtERPT3c4DdcRofACAiI4G7gVNUdXPlroYxtYOvxPPfZdv4ZukW1uzIZc2OXManrSU1LpLTOzXgjM4N6d821e7jMRVSZclGVfNE5FPgYRG5Dqc12vnAyT6qvw3cLiJf4bRGuwN40S1LA4qAW0XkFeAv7vjpACJyBfA4MERV1wVodYypVTwTz3fT9xDdohvfLt/Ot8u3k7Ennw/mpfPBvHSiw0MZ2C6VUzs2YHCHBtZzgfFbVTd9vgn4N7ADyARuVNVlIjIQ+FpVi69gTQDaAEvc96+741DVQhEZ5o77J7ACGOY2ewZ4FEgB5nncmvOuqt4Q0DUzppYICxH6t02lf9tUHjivMyu27uW7FU7iWZKRffgR1wAdG8UzqEN9BrWvT9+WyUSEWUfyxrcqTTaqmoVz/4z3+O9xLvwXv1fgLnfwNZ+FQJ8SylpXSrDGGESEzk0S6NwkgVtPa8fW7Hy+W7GDmat28sPaXazctpeV2/YyYeY64iLDOPm4FAZ3aMCgDvVpmhQd7PBNNWLd1Rhj/NY4MZorT2zJlSe2ZP/BIuZv2M3M1TtJW7WD1dtzjzjqadsgjgFtUzn5uBT6tUmxRyPUcZZsjDEVEhkWevh0273ndCJjTz4zV+1k5uodzFmTebiRwcQfNhAi0K1pIie7yadvy2SiI6yhQV1iycYYUymaJkUzvF8LhvdrQeHBQ/yavoc5a3bx49pMFqbvPty0enzaWiJCQ+jdMomTj0ulf9sUujVNsus9tZwlG2NMpYsIC+GE1smc0DqZ0WfAvsKD/Lw+ix/WZvLD2l0s25LDT+uy+GldFs98C1HhIfRsnsQJrZI5vnUyvVvUIzbSdk+1if03jTEBFxMRxuAOTnNpgN15hfy0LpMf1mby4zrnlFtx8gGnV+suTRI4vlWyO9QjJS4ymKtgjpElG2NMlasXG3H4vh6AzNz9zN+4m3nrs5i3IYulW3JYvDmbxZuzeWP2egCOqx9L08j9bIvZRK8W9WjbII5Q69mgxrBkY4wJupS4SIZ2acTQLo0AyNt/kIWb9vDzhizmrc9iYfpu1u7MYy0wa7Nz+11sRCg9mifRq0USPZvXo2fzJOrH29FPdWXJxhhT7cRGhjGgXSoD2qUCUHjwEEu3ZPPR9PnsjUhh4aY9ZOzJd68BZR6ernlyND2b16NX8yR6NE+kc+NEa/VWTViyMcZUexFhIfRuUY+cVuEMHtwbgB05BSxM38Ov6XtYuGk3izdnk56VT3pWPl8s2gJAiMBx9ePo1jSRru7QpUmCNT4IAtvixpgaqUFC1BGn3g4WHeK3Hbks3OQknyUZ2fy2I/fw8OnCDABEoE1qLF2bJtKtaSJdmiTSpWkCCVF202kgWbIxxtQKYaEhdGqcQKfGCQzv1wKAggNFrNy2lyUZ2SzLyGZJRjart+91rv/szGPKr1sOT9+sXjQdG8XTsVECHRvH07FRPK1SYgkLtft/KoMlG2NMrRUVHkrP5kn0bJ50eNz+g0Ws3pbL0i3Zh5PQim172bw7n827nb7fikWEhdC+YRwdGibQqbGTiDo0ireGCBVgycYYU6dEhoXSrVki3Zolcrk77mDRITZk5jkdi27dy8ptOazYupeMPfkszchhacaRDxlOiY2gbYO4w0O7BvHsLjiEquLR27zxYMnGGFPnhYWG0LZBPG0bxHNu99/H5xQcYPW2vazYtpeVW3NY5fZynZlXSOb6LOauzzpiPv/48RvaNIijbf042jV0/rZtEEfz5Jg6f0+QJRtjjClBQlQ4fVsl07dV8uFxqkrGnvzDHY2u3en8XZGxm737D7IofQ+L0vccMZ+IsBBap8TSMiWG1qmxtEqNpVVKLK1SY2gYH1UnHrttycYYY8pBRGhWL4Zm9WIOd78DkJaWRre+J/Gbm4Q8E9HW7AJWbd/Lqu17j5pfVHgIrdxE1Co1ltYpvyejhgmRtea0nCUbY4ypJClxkaTERXJim5Qjxu8tOMCGXfvYkJnHhl15rHf/bszcR2Ze4eGH0HmLDg+lWb1omtWLpnlyDM3rxRzxOjGm5jTXtmRjjDEBFh8VfrhRgrfs/ANszMxj/a48Nuza57x2k9HufQcO3yfke75hNKsXQ3M3ATWrF03zejE0T46hcVJUtbp3yJKNMcYEUWJ0ON2bJdG9WdJRZdn5B9i8ex/pWfls3r2PzbvzSc/aR7o7bm/BQVZszWHF1hwfc4a4yDAaJ0YRWVTA17sW0zgpiiaJ0TROiqJxYjRNkqKIiaiaNGDJxhhjqqnE6HASo51eDrypKll5haTvzj+ckNLdhLQ5ax9bsvPJ3X/w8FHR0sx0n8tIiAqjSVI0jROjaJwUTZPEKBokRNEwIYqGCZE0iI+iXkz4MV87smRjjDE1kIgcvkbkedNqMVUlO/8AW/YU8M3sn0lp0Y6te/LZll3Alux8tmYXsDW7gJyCg+SUcM2oWERoCPXjI2mQEEnD+Cjnb0IUDcpxc6slG2OMqYVEhKSYCJJiItjRIIzBJ7Y8qo6qkplX6CSgPU4C2pKdz86c/WzfW8COnP1sz3ESUsaefDL25Fc4Hks2xhhTR4kIqXGRpMZF0rXp0afqihUcKHISz94CtucUHH69I2c/z/m5LEs2xhhjShUVHkqLlBhapMQcVfbcn/ybh3VnaowxJuAs2RhjjAk4SzbGGGMCzpKNMcaYgLNkY4wxJuAs2RhjjAm4Kk02IpIsIpNFJE9ENorI8BLqiYiMFZFMdxgrHn0liEhPEVkgIvvcvz39ndYYY0zVq+ojm5eAQqAhcAUwXkS6+Kg3ChgG9AC6A+cB1wOISAQwBXgXqAe8BUxxx5c6rTHGmOCosmQjIrHARcD9qpqrqrOBz4ErfVS/GhinqptVNQMYB4xwywbj3Iz6nKruV9UXAAFO9WNaY4wxQVCVPQi0Bw6q6mqPcYuAQT7qdnHLPOt18ShbrKrqUb7YHf/fMqY9goiMwjkSAtgvIkv9W5UqlwrsCnYQJbDYKsZiqxiLrWICGdvRna75UJXJJg7wfuhCNhBfQt1sr3px7rUX7zLv+ZQ4rVeCQlVfBV4FEJH5qtrX/9WpOhZbxVhsFWOxVYzFVrqqvGaTCyR4jUsAfPVr7V03Ach1k0VZ8yltWmOMMUFQlclmNRAmIu08xvUAlvmou8wt81VvGdDdq4VZd6/ykqY1xhgTBFWWbFQ1D/gUeFhEYkWkP3A+8I6P6m8Dt4tIUxFpAtwBTHTL0oAi4FYRiRSRW9zx0/2YtjSvln+tqozFVjEWW8VYbBVjsZVCqvLskogkA/8GzgAygbtV9X0RGQh8rapxbj0BxgLXuZO+Dvyt+FSYiPRyx3UGVgDXqupCf6Y1xhhT9ao02RhjjKmbrLsaY4wxAWfJxhhjTMDV+WTjb39tlbi8NBEpEJFcd1jlUTbcjSFPRD5zr3H5FWdp05YSyy0iMl9E9ovIRK+y00Rkpdv/3AwRaelRFiki/xaRHBHZJiK3V9a0ZcUmIq1ERD22X66I3F9Vsbl13nC39V4R+VVEzq4O26202IK93dx674rIVrfeahG5rjLmH8jYqsN286jfTpx9x7se4wKyzyhr2gpR1To9AP8BPsS5GXQAzk2gXQK4vDTgOh/ju+DcK3SKG8v7wAf+xFnWtKXEciFOP3LjgYke41Pd+V8CRAFPAT95lD8BfI/TN10nYBtw1rFO62dsrQAFwkpYp4DGBsQCD7pxhADnutu+VbC3WxmxBXW7eXxOI93XHd16fYK93cqILejbzaP+N279dwO9zyht2grv+wK1U60JA86XsxBo7zHuHeCfAVxmGr6TzePA+x7vj3Njiy8rztKm9TOmRzlyhz4K+MFrO+UDHd33W4AzPcofKf6gHsu0fsZW1pe/ymLzqLcYp9+/arPdfMRWrbYb0AHYClxa3babV2zVYrsBfwIm4fyYKE42AdlnlDVtRYe6fhqtpP7afPalVomeEJFdIjJHRAa7447o001V1+L+w/2Is7RpK8J7fnnAWqCLiNQDGlN633UVnbY8NorIZhF5U0RSAYIRm4g0xNnOy45x/oGOrVhQt5uIvCwi+4CVODv0r45x/oGOrVjQtpuIJAAPA96n2QK1zwjIfrGuJ5vy9NdWWf4GtAGa4txo9YWIHEfpfb6VFWdZ/cWVV1mxwNH9z/kTS1nT+mMXcDxO53993Gnf81h2lcUmIuHust9S1ZXHOP9Ax1Yttpuq3uSWDcS5yXv/Mc4/0LFVh+32CPCGqm72Gh+ofUZA9ot1PdmUp7+2SqGqc1V1rzqPR3gLmAOcU0Ys5e0Pzru8vMqKBY7uf86fWMqatkzqPJ5ivqoeVNXtwC3AmSISX5WxiUgIzqmFQjeGY51/QGOrLtvNjaVInUeMNANuPMb5BzS2YG83cR4MeTrwrI9wA7XPCMh+sa4nm/L01xYoivM8niP6dBORNkCkG2NZcZY2bUV4zy8W55zuMlXdjXOKobS+6yo6bUUU35UcUlWxiYgAb+A8BPAiVT1QCfMPdGzeqny7+RBWPJ9jmH+gY/NW1dttMM51o00isg0YA1wkIr/4mH9l7TMCs188lgs+tWEAPsBpeREL9CeArdGAJGAoTsuUMJynlebhnCPtgnPoOtCN5V2ObB1SYpxlTVtKPGFuLE/g/BIujqu+O/+L3HFjObIVzT+BmTitaDrifGmKW+BUeFo/Y+uHcwE3BEjBaTEzo4pjewX4CYjzGl8dtltJsQV1uwENcC5yxwGhON+DPOCPwd5uZcQW7O0WAzTyGJ4GPnbnHbB9RmnTVnj/F4idak0agGTgM/fDtQkYHsBl1Qfm4RyO7sHZKZzhUT7cjSEP59HXyf7GWdq0pcTzIM4vNc/hQbfsdJwLpfk4LehaeUwXidPHXQ6wHbjda74Vnras2IDLgfXuem7F6Xi1UVXFhnPuXoECnNMNxcMVwd5upcVWDbZbfZwd6x633hLgL5Ux/0DGFuztVsL34t1A7zPKmrYig/WNZowxJuDq+jUbY4wxVcCSjTHGmICzZGOMMSbgLNkYY4wJOEs2xhhjAs6SjTHGmICzZGNMLSMiI0Qkt+yaxlQdSzbGBIiITHQfvFU87BKRqSLSsRzzeFBElgYyTmOqgiUbYwLrO5yu5BsDZwLRwOSgRmRMEFiyMSaw9qvqNnf4Baf33o4iEg0gIv8UkVUiki8iG0TkSRGJcstGAA/gPP+k+OhohFuWKCLjxXmUcYGIrBCRyzwXLM4jiZe6j/adISKtq3LFjfEUFuwAjKkr3G7pLwOWqGq+OzoPGAlkAJ1xOtLcD9yP0+ljV5zHOw9262e7PTt/hdOB4zU4vfR2wOnssVgkcI877wLgLXfeQwOzdsaUzpKNMYF1lsfF+lggHef5RQCo6iMedTeIyOM43cjfr6r57rQHVXVbcSUROQM4CacX3hXu6HVeyw0DblbVVe40TwP/FhFR6xDRBIGdRjMmsGYBPd3hBOB/wDci0hxARC4Wkdkiss1NLM8CLcqYZy9gq0ei8WV/caJxbQEicI6GjKlylmyMCax9qrrGHeYB1+E89XCUiJyI89yQacB5OEnkPiC8EpZ78P/bu1uViKIoDMPvSgY12BTDFItVMFk1eAGGwavwBrwBi17IgDBZsIiYBIvVIgajzTDLsESGU0RxyYT3SefA3gdO+tg/8A3ev0q//uDb0o+5jSb9rwRmVCnWHvA8v5UWEaPB+Heq0GvePbAREdvfrG6khWHYSL2WImL983mN6rBfAabAKrAZEcfALXV4Px7MfwJGEbFDlVi9UVtxd8AkIk6oCwJbwHJmXsVYJPQAAABsSURBVPb+jvQ7LqmlXvtUw+MLFRC7wFFmXmfmFDgDzoEH4AA4HcyfUDfProBXYJyZM+AQuKHqfB+BC+pMRlpINnVKktq5spEktTNsJEntDBtJUjvDRpLUzrCRJLUzbCRJ7QwbSVI7w0aS1O4D96wB2VjPCZQAAAAASUVORK5CYII=\n",
      "text/plain": [
       "<Figure size 432x288 with 1 Axes>"
      ]
     },
     "metadata": {
      "needs_background": "light"
     },
     "output_type": "display_data"
    }
   ],
   "source": [
    "plt.plot(steps, lrs, \"-\", linewidth=2)\n",
    "plt.axis([0, n_steps - 1, 0, lr0 * 1.1])\n",
    "plt.xlabel(\"Batch\")\n",
    "plt.ylabel(\"Learning Rate\")\n",
    "plt.title(\"Exponential Scheduling (per batch)\", fontsize=14)\n",
    "plt.grid(True)\n",
    "plt.show()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Piecewise Constant Scheduling"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 87,
   "metadata": {},
   "outputs": [],
   "source": [
    "def piecewise_constant_fn(epoch):\n",
    "    if epoch < 5:\n",
    "        return 0.01\n",
    "    elif epoch < 15:\n",
    "        return 0.005\n",
    "    else:\n",
    "        return 0.001"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 88,
   "metadata": {},
   "outputs": [],
   "source": [
    "def piecewise_constant(boundaries, values):\n",
    "    boundaries = np.array([0] + boundaries)\n",
    "    values = np.array(values)\n",
    "    def piecewise_constant_fn(epoch):\n",
    "        return values[np.argmax(boundaries > epoch) - 1]\n",
    "    return piecewise_constant_fn\n",
    "\n",
    "piecewise_constant_fn = piecewise_constant([5, 15], [0.01, 0.005, 0.001])"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 89,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Train on 55000 samples, validate on 5000 samples\n",
      "Epoch 1/25\n",
      "55000/55000 [==============================] - 6s 111us/sample - loss: 0.8151 - accuracy: 0.7655 - val_loss: 0.6868 - val_accuracy: 0.7780\n",
      "Epoch 2/25\n",
      "55000/55000 [==============================] - 6s 102us/sample - loss: 0.8153 - accuracy: 0.7659 - val_loss: 1.0604 - val_accuracy: 0.7148\n",
      "Epoch 3/25\n",
      "55000/55000 [==============================] - 6s 104us/sample - loss: 0.9138 - accuracy: 0.7218 - val_loss: 1.3223 - val_accuracy: 0.6660\n",
      "Epoch 4/25\n",
      "55000/55000 [==============================] - 6s 103us/sample - loss: 0.8506 - accuracy: 0.7627 - val_loss: 0.6807 - val_accuracy: 0.8174\n",
      "Epoch 5/25\n",
      "55000/55000 [==============================] - 6s 101us/sample - loss: 0.7213 - accuracy: 0.8068 - val_loss: 1.0441 - val_accuracy: 0.8030\n",
      "Epoch 6/25\n",
      "55000/55000 [==============================] - 6s 101us/sample - loss: 0.4882 - accuracy: 0.8548 - val_loss: 0.5411 - val_accuracy: 0.8494\n",
      "Epoch 7/25\n",
      "55000/55000 [==============================] - 6s 101us/sample - loss: 0.4721 - accuracy: 0.8568 - val_loss: 0.5808 - val_accuracy: 0.8448\n",
      "Epoch 8/25\n",
      "55000/55000 [==============================] - 6s 101us/sample - loss: 0.4412 - accuracy: 0.8659 - val_loss: 0.5466 - val_accuracy: 0.8526\n",
      "Epoch 9/25\n",
      "55000/55000 [==============================] - 6s 100us/sample - loss: 0.4234 - accuracy: 0.8718 - val_loss: 0.5611 - val_accuracy: 0.8528\n",
      "Epoch 10/25\n",
      "55000/55000 [==============================] - 5s 99us/sample - loss: 0.4300 - accuracy: 0.8721 - val_loss: 0.5049 - val_accuracy: 0.8650\n",
      "Epoch 11/25\n",
      "55000/55000 [==============================] - 5s 100us/sample - loss: 0.4162 - accuracy: 0.8768 - val_loss: 0.5957 - val_accuracy: 0.8534\n",
      "Epoch 12/25\n",
      "55000/55000 [==============================] - 6s 101us/sample - loss: 0.4122 - accuracy: 0.8780 - val_loss: 0.5707 - val_accuracy: 0.8640\n",
      "Epoch 13/25\n",
      "55000/55000 [==============================] - 6s 101us/sample - loss: 0.3951 - accuracy: 0.8833 - val_loss: 0.5523 - val_accuracy: 0.8690\n",
      "Epoch 14/25\n",
      "55000/55000 [==============================] - 5s 100us/sample - loss: 0.3961 - accuracy: 0.8834 - val_loss: 0.7371 - val_accuracy: 0.8452\n",
      "Epoch 15/25\n",
      "55000/55000 [==============================] - 5s 100us/sample - loss: 0.4201 - accuracy: 0.8839 - val_loss: 0.6546 - val_accuracy: 0.8558\n",
      "Epoch 16/25\n",
      "55000/55000 [==============================] - 6s 100us/sample - loss: 0.2645 - accuracy: 0.9162 - val_loss: 0.4655 - val_accuracy: 0.8844\n",
      "Epoch 17/25\n",
      "55000/55000 [==============================] - 6s 100us/sample - loss: 0.2440 - accuracy: 0.9222 - val_loss: 0.4758 - val_accuracy: 0.8830\n",
      "Epoch 18/25\n",
      "55000/55000 [==============================] - 6s 100us/sample - loss: 0.2320 - accuracy: 0.9256 - val_loss: 0.4917 - val_accuracy: 0.8880\n",
      "Epoch 19/25\n",
      "55000/55000 [==============================] - 6s 100us/sample - loss: 0.2248 - accuracy: 0.9279 - val_loss: 0.4644 - val_accuracy: 0.8878\n",
      "Epoch 20/25\n",
      "55000/55000 [==============================] - 6s 100us/sample - loss: 0.2172 - accuracy: 0.9302 - val_loss: 0.5036 - val_accuracy: 0.8848\n",
      "Epoch 21/25\n",
      "55000/55000 [==============================] - 6s 100us/sample - loss: 0.2139 - accuracy: 0.9327 - val_loss: 0.4921 - val_accuracy: 0.8914\n",
      "Epoch 22/25\n",
      "55000/55000 [==============================] - 6s 101us/sample - loss: 0.2030 - accuracy: 0.9360 - val_loss: 0.5197 - val_accuracy: 0.8860\n",
      "Epoch 23/25\n",
      "55000/55000 [==============================] - 5s 100us/sample - loss: 0.2014 - accuracy: 0.9360 - val_loss: 0.5231 - val_accuracy: 0.8892\n",
      "Epoch 24/25\n",
      "55000/55000 [==============================] - 5s 100us/sample - loss: 0.1912 - accuracy: 0.9391 - val_loss: 0.5223 - val_accuracy: 0.8876\n",
      "Epoch 25/25\n",
      "55000/55000 [==============================] - 5s 99us/sample - loss: 0.1872 - accuracy: 0.9418 - val_loss: 0.5068 - val_accuracy: 0.8886\n"
     ]
    }
   ],
   "source": [
    "lr_scheduler = keras.callbacks.LearningRateScheduler(piecewise_constant_fn)\n",
    "\n",
    "model = keras.models.Sequential([\n",
    "    keras.layers.Flatten(input_shape=[28, 28]),\n",
    "    keras.layers.Dense(300, activation=\"selu\", kernel_initializer=\"lecun_normal\"),\n",
    "    keras.layers.Dense(100, activation=\"selu\", kernel_initializer=\"lecun_normal\"),\n",
    "    keras.layers.Dense(10, activation=\"softmax\")\n",
    "])\n",
    "model.compile(loss=\"sparse_categorical_crossentropy\", optimizer=\"nadam\", metrics=[\"accuracy\"])\n",
    "n_epochs = 25\n",
    "history = model.fit(X_train_scaled, y_train, epochs=n_epochs,\n",
    "                    validation_data=(X_valid_scaled, y_valid),\n",
    "                    callbacks=[lr_scheduler])"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 90,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "image/png": "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\n",
      "text/plain": [
       "<Figure size 432x288 with 1 Axes>"
      ]
     },
     "metadata": {
      "needs_background": "light"
     },
     "output_type": "display_data"
    }
   ],
   "source": [
    "plt.plot(history.epoch, [piecewise_constant_fn(epoch) for epoch in history.epoch], \"o-\")\n",
    "plt.axis([0, n_epochs - 1, 0, 0.011])\n",
    "plt.xlabel(\"Epoch\")\n",
    "plt.ylabel(\"Learning Rate\")\n",
    "plt.title(\"Piecewise Constant Scheduling\", fontsize=14)\n",
    "plt.grid(True)\n",
    "plt.show()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Performance Scheduling"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 91,
   "metadata": {},
   "outputs": [],
   "source": [
    "tf.random.set_seed(42)\n",
    "np.random.seed(42)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 92,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Train on 55000 samples, validate on 5000 samples\n",
      "Epoch 1/25\n",
      "55000/55000 [==============================] - 4s 79us/sample - loss: 0.5954 - accuracy: 0.8055 - val_loss: 0.5432 - val_accuracy: 0.8154\n",
      "Epoch 2/25\n",
      "55000/55000 [==============================] - 4s 74us/sample - loss: 0.5194 - accuracy: 0.8345 - val_loss: 0.5184 - val_accuracy: 0.8468\n",
      "Epoch 3/25\n",
      "55000/55000 [==============================] - 4s 73us/sample - loss: 0.5080 - accuracy: 0.8453 - val_loss: 0.5780 - val_accuracy: 0.8384\n",
      "Epoch 4/25\n",
      "55000/55000 [==============================] - 4s 73us/sample - loss: 0.5360 - accuracy: 0.8452 - val_loss: 0.7195 - val_accuracy: 0.8350\n",
      "Epoch 5/25\n",
      "55000/55000 [==============================] - 4s 74us/sample - loss: 0.5239 - accuracy: 0.8504 - val_loss: 0.5219 - val_accuracy: 0.8562\n",
      "Epoch 6/25\n",
      "55000/55000 [==============================] - 4s 74us/sample - loss: 0.5163 - accuracy: 0.8528 - val_loss: 0.5669 - val_accuracy: 0.8382\n",
      "Epoch 7/25\n",
      "55000/55000 [==============================] - 4s 74us/sample - loss: 0.5088 - accuracy: 0.8561 - val_loss: 0.6591 - val_accuracy: 0.8268\n",
      "Epoch 8/25\n",
      "55000/55000 [==============================] - 4s 77us/sample - loss: 0.3022 - accuracy: 0.8938 - val_loss: 0.3955 - val_accuracy: 0.8834\n",
      "Epoch 9/25\n",
      "55000/55000 [==============================] - 4s 76us/sample - loss: 0.2501 - accuracy: 0.9087 - val_loss: 0.4060 - val_accuracy: 0.8792\n",
      "Epoch 10/25\n",
      "55000/55000 [==============================] - 4s 75us/sample - loss: 0.2304 - accuracy: 0.9158 - val_loss: 0.3998 - val_accuracy: 0.8846\n",
      "Epoch 11/25\n",
      "55000/55000 [==============================] - 4s 75us/sample - loss: 0.2155 - accuracy: 0.9206 - val_loss: 0.3880 - val_accuracy: 0.8898\n",
      "Epoch 12/25\n",
      "55000/55000 [==============================] - 4s 75us/sample - loss: 0.2034 - accuracy: 0.9253 - val_loss: 0.4049 - val_accuracy: 0.8838\n",
      "Epoch 13/25\n",
      "55000/55000 [==============================] - 4s 77us/sample - loss: 0.1878 - accuracy: 0.9285 - val_loss: 0.4440 - val_accuracy: 0.8838\n",
      "Epoch 14/25\n",
      "55000/55000 [==============================] - 4s 80us/sample - loss: 0.1839 - accuracy: 0.9325 - val_loss: 0.4478 - val_accuracy: 0.8838\n",
      "Epoch 15/25\n",
      "55000/55000 [==============================] - 4s 76us/sample - loss: 0.1747 - accuracy: 0.9348 - val_loss: 0.5072 - val_accuracy: 0.8806\n",
      "Epoch 16/25\n",
      "55000/55000 [==============================] - 4s 75us/sample - loss: 0.1689 - accuracy: 0.9367 - val_loss: 0.4897 - val_accuracy: 0.8790\n",
      "Epoch 17/25\n",
      "55000/55000 [==============================] - 4s 78us/sample - loss: 0.1090 - accuracy: 0.9576 - val_loss: 0.4571 - val_accuracy: 0.8900\n",
      "Epoch 18/25\n",
      "55000/55000 [==============================] - 4s 74us/sample - loss: 0.0926 - accuracy: 0.9639 - val_loss: 0.4563 - val_accuracy: 0.8934\n",
      "Epoch 19/25\n",
      "55000/55000 [==============================] - 4s 75us/sample - loss: 0.0861 - accuracy: 0.9671 - val_loss: 0.5103 - val_accuracy: 0.8898\n",
      "Epoch 20/25\n",
      "55000/55000 [==============================] - 4s 75us/sample - loss: 0.0794 - accuracy: 0.9692 - val_loss: 0.5065 - val_accuracy: 0.8936\n",
      "Epoch 21/25\n",
      "55000/55000 [==============================] - 4s 75us/sample - loss: 0.0737 - accuracy: 0.9721 - val_loss: 0.5516 - val_accuracy: 0.8928\n",
      "Epoch 22/25\n",
      "55000/55000 [==============================] - 4s 76us/sample - loss: 0.0547 - accuracy: 0.9803 - val_loss: 0.5315 - val_accuracy: 0.8944\n",
      "Epoch 23/25\n",
      "55000/55000 [==============================] - 4s 78us/sample - loss: 0.0487 - accuracy: 0.9827 - val_loss: 0.5429 - val_accuracy: 0.8928\n",
      "Epoch 24/25\n",
      "55000/55000 [==============================] - 4s 80us/sample - loss: 0.0455 - accuracy: 0.9844 - val_loss: 0.5554 - val_accuracy: 0.8918\n",
      "Epoch 25/25\n",
      "55000/55000 [==============================] - 4s 79us/sample - loss: 0.0427 - accuracy: 0.9850 - val_loss: 0.5730 - val_accuracy: 0.8920\n"
     ]
    }
   ],
   "source": [
    "lr_scheduler = keras.callbacks.ReduceLROnPlateau(factor=0.5, patience=5)\n",
    "\n",
    "model = keras.models.Sequential([\n",
    "    keras.layers.Flatten(input_shape=[28, 28]),\n",
    "    keras.layers.Dense(300, activation=\"selu\", kernel_initializer=\"lecun_normal\"),\n",
    "    keras.layers.Dense(100, activation=\"selu\", kernel_initializer=\"lecun_normal\"),\n",
    "    keras.layers.Dense(10, activation=\"softmax\")\n",
    "])\n",
    "optimizer = keras.optimizers.SGD(lr=0.02, momentum=0.9)\n",
    "model.compile(loss=\"sparse_categorical_crossentropy\", optimizer=optimizer, metrics=[\"accuracy\"])\n",
    "n_epochs = 25\n",
    "history = model.fit(X_train_scaled, y_train, epochs=n_epochs,\n",
    "                    validation_data=(X_valid_scaled, y_valid),\n",
    "                    callbacks=[lr_scheduler])"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 93,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "image/png": "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\n",
      "text/plain": [
       "<Figure size 432x288 with 2 Axes>"
      ]
     },
     "metadata": {
      "needs_background": "light"
     },
     "output_type": "display_data"
    }
   ],
   "source": [
    "plt.plot(history.epoch, history.history[\"lr\"], \"bo-\")\n",
    "plt.xlabel(\"Epoch\")\n",
    "plt.ylabel(\"Learning Rate\", color='b')\n",
    "plt.tick_params('y', colors='b')\n",
    "plt.gca().set_xlim(0, n_epochs - 1)\n",
    "plt.grid(True)\n",
    "\n",
    "ax2 = plt.gca().twinx()\n",
    "ax2.plot(history.epoch, history.history[\"val_loss\"], \"r^-\")\n",
    "ax2.set_ylabel('Validation Loss', color='r')\n",
    "ax2.tick_params('y', colors='r')\n",
    "\n",
    "plt.title(\"Reduce LR on Plateau\", fontsize=14)\n",
    "plt.show()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### tf.keras schedulers"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 94,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Train on 55000 samples, validate on 5000 samples\n",
      "Epoch 1/25\n",
      "55000/55000 [==============================] - 4s 77us/sample - loss: 0.4887 - accuracy: 0.8282 - val_loss: 0.4245 - val_accuracy: 0.8526\n",
      "Epoch 2/25\n",
      "55000/55000 [==============================] - 4s 71us/sample - loss: 0.3830 - accuracy: 0.8641 - val_loss: 0.3798 - val_accuracy: 0.8688\n",
      "Epoch 3/25\n",
      "55000/55000 [==============================] - 4s 71us/sample - loss: 0.3491 - accuracy: 0.8758 - val_loss: 0.3650 - val_accuracy: 0.8730\n",
      "Epoch 4/25\n",
      "55000/55000 [==============================] - 4s 78us/sample - loss: 0.3267 - accuracy: 0.8839 - val_loss: 0.3564 - val_accuracy: 0.8746\n",
      "Epoch 5/25\n",
      "55000/55000 [==============================] - 4s 72us/sample - loss: 0.3102 - accuracy: 0.8893 - val_loss: 0.3493 - val_accuracy: 0.8770\n",
      "Epoch 6/25\n",
      "55000/55000 [==============================] - 4s 73us/sample - loss: 0.2969 - accuracy: 0.8939 - val_loss: 0.3400 - val_accuracy: 0.8818\n",
      "Epoch 7/25\n",
      "55000/55000 [==============================] - 4s 77us/sample - loss: 0.2855 - accuracy: 0.8983 - val_loss: 0.3385 - val_accuracy: 0.8830\n",
      "Epoch 8/25\n",
      "55000/55000 [==============================] - 4s 68us/sample - loss: 0.2764 - accuracy: 0.9025 - val_loss: 0.3372 - val_accuracy: 0.8824\n",
      "Epoch 9/25\n",
      "55000/55000 [==============================] - 4s 67us/sample - loss: 0.2684 - accuracy: 0.9039 - val_loss: 0.3337 - val_accuracy: 0.8848\n",
      "Epoch 10/25\n",
      "55000/55000 [==============================] - 4s 73us/sample - loss: 0.2613 - accuracy: 0.9072 - val_loss: 0.3277 - val_accuracy: 0.8862\n",
      "Epoch 11/25\n",
      "55000/55000 [==============================] - 4s 71us/sample - loss: 0.2555 - accuracy: 0.9086 - val_loss: 0.3273 - val_accuracy: 0.8860\n",
      "Epoch 12/25\n",
      "55000/55000 [==============================] - 4s 73us/sample - loss: 0.2500 - accuracy: 0.9111 - val_loss: 0.3244 - val_accuracy: 0.8840\n",
      "Epoch 13/25\n",
      "55000/55000 [==============================] - 4s 73us/sample - loss: 0.2454 - accuracy: 0.9124 - val_loss: 0.3194 - val_accuracy: 0.8904\n",
      "Epoch 14/25\n",
      "55000/55000 [==============================] - 4s 71us/sample - loss: 0.2414 - accuracy: 0.9141 - val_loss: 0.3226 - val_accuracy: 0.8884\n",
      "Epoch 15/25\n",
      "55000/55000 [==============================] - 4s 73us/sample - loss: 0.2378 - accuracy: 0.9160 - val_loss: 0.3233 - val_accuracy: 0.8860\n",
      "Epoch 16/25\n",
      "55000/55000 [==============================] - 4s 69us/sample - loss: 0.2347 - accuracy: 0.9174 - val_loss: 0.3207 - val_accuracy: 0.8904\n",
      "Epoch 17/25\n",
      "55000/55000 [==============================] - 4s 71us/sample - loss: 0.2318 - accuracy: 0.9179 - val_loss: 0.3195 - val_accuracy: 0.8892\n",
      "Epoch 18/25\n",
      "55000/55000 [==============================] - 4s 69us/sample - loss: 0.2293 - accuracy: 0.9193 - val_loss: 0.3184 - val_accuracy: 0.8916\n",
      "Epoch 19/25\n",
      "55000/55000 [==============================] - 4s 67us/sample - loss: 0.2272 - accuracy: 0.9201 - val_loss: 0.3196 - val_accuracy: 0.8886\n",
      "Epoch 20/25\n",
      "55000/55000 [==============================] - 4s 68us/sample - loss: 0.2253 - accuracy: 0.9206 - val_loss: 0.3190 - val_accuracy: 0.8918\n",
      "Epoch 21/25\n",
      "55000/55000 [==============================] - 4s 68us/sample - loss: 0.2235 - accuracy: 0.9214 - val_loss: 0.3176 - val_accuracy: 0.8912\n",
      "Epoch 22/25\n",
      "55000/55000 [==============================] - 4s 69us/sample - loss: 0.2220 - accuracy: 0.9220 - val_loss: 0.3181 - val_accuracy: 0.8900\n",
      "Epoch 23/25\n",
      "55000/55000 [==============================] - 4s 71us/sample - loss: 0.2206 - accuracy: 0.9226 - val_loss: 0.3187 - val_accuracy: 0.8894\n",
      "Epoch 24/25\n",
      "55000/55000 [==============================] - 4s 68us/sample - loss: 0.2193 - accuracy: 0.9231 - val_loss: 0.3168 - val_accuracy: 0.8908\n",
      "Epoch 25/25\n",
      "55000/55000 [==============================] - 4s 68us/sample - loss: 0.2181 - accuracy: 0.9234 - val_loss: 0.3171 - val_accuracy: 0.8898\n"
     ]
    }
   ],
   "source": [
    "model = keras.models.Sequential([\n",
    "    keras.layers.Flatten(input_shape=[28, 28]),\n",
    "    keras.layers.Dense(300, activation=\"selu\", kernel_initializer=\"lecun_normal\"),\n",
    "    keras.layers.Dense(100, activation=\"selu\", kernel_initializer=\"lecun_normal\"),\n",
    "    keras.layers.Dense(10, activation=\"softmax\")\n",
    "])\n",
    "s = 20 * len(X_train) // 32 # number of steps in 20 epochs (batch size = 32)\n",
    "learning_rate = keras.optimizers.schedules.ExponentialDecay(0.01, s, 0.1)\n",
    "optimizer = keras.optimizers.SGD(learning_rate)\n",
    "model.compile(loss=\"sparse_categorical_crossentropy\", optimizer=optimizer, metrics=[\"accuracy\"])\n",
    "n_epochs = 25\n",
    "history = model.fit(X_train_scaled, y_train, epochs=n_epochs,\n",
    "                    validation_data=(X_valid_scaled, y_valid))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "For piecewise constant scheduling, try this:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 95,
   "metadata": {},
   "outputs": [],
   "source": [
    "learning_rate = keras.optimizers.schedules.PiecewiseConstantDecay(\n",
    "    boundaries=[5. * n_steps_per_epoch, 15. * n_steps_per_epoch],\n",
    "    values=[0.01, 0.005, 0.001])"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 1Cycle scheduling"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 96,
   "metadata": {},
   "outputs": [],
   "source": [
    "K = keras.backend\n",
    "\n",
    "class ExponentialLearningRate(keras.callbacks.Callback):\n",
    "    def __init__(self, factor):\n",
    "        self.factor = factor\n",
    "        self.rates = []\n",
    "        self.losses = []\n",
    "    def on_batch_end(self, batch, logs):\n",
    "        self.rates.append(K.get_value(self.model.optimizer.lr))\n",
    "        self.losses.append(logs[\"loss\"])\n",
    "        K.set_value(self.model.optimizer.lr, self.model.optimizer.lr * self.factor)\n",
    "\n",
    "def find_learning_rate(model, X, y, epochs=1, batch_size=32, min_rate=10**-5, max_rate=10):\n",
    "    init_weights = model.get_weights()\n",
    "    iterations = len(X) // batch_size * epochs\n",
    "    factor = np.exp(np.log(max_rate / min_rate) / iterations)\n",
    "    init_lr = K.get_value(model.optimizer.lr)\n",
    "    K.set_value(model.optimizer.lr, min_rate)\n",
    "    exp_lr = ExponentialLearningRate(factor)\n",
    "    history = model.fit(X, y, epochs=epochs, batch_size=batch_size,\n",
    "                        callbacks=[exp_lr])\n",
    "    K.set_value(model.optimizer.lr, init_lr)\n",
    "    model.set_weights(init_weights)\n",
    "    return exp_lr.rates, exp_lr.losses\n",
    "\n",
    "def plot_lr_vs_loss(rates, losses):\n",
    "    plt.plot(rates, losses)\n",
    "    plt.gca().set_xscale('log')\n",
    "    plt.hlines(min(losses), min(rates), max(rates))\n",
    "    plt.axis([min(rates), max(rates), min(losses), (losses[0] + min(losses)) / 2])\n",
    "    plt.xlabel(\"Learning rate\")\n",
    "    plt.ylabel(\"Loss\")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 97,
   "metadata": {},
   "outputs": [],
   "source": [
    "tf.random.set_seed(42)\n",
    "np.random.seed(42)\n",
    "\n",
    "model = keras.models.Sequential([\n",
    "    keras.layers.Flatten(input_shape=[28, 28]),\n",
    "    keras.layers.Dense(300, activation=\"selu\", kernel_initializer=\"lecun_normal\"),\n",
    "    keras.layers.Dense(100, activation=\"selu\", kernel_initializer=\"lecun_normal\"),\n",
    "    keras.layers.Dense(10, activation=\"softmax\")\n",
    "])\n",
    "model.compile(loss=\"sparse_categorical_crossentropy\",\n",
    "              optimizer=keras.optimizers.SGD(lr=1e-3),\n",
    "              metrics=[\"accuracy\"])"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 98,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Train on 55000 samples\n",
      "55000/55000 [==============================] - 2s 28us/sample - loss: nan - accuracy: 0.3888\n"
     ]
    },
    {
     "data": {
      "image/png": "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\n",
      "text/plain": [
       "<Figure size 432x288 with 1 Axes>"
      ]
     },
     "metadata": {
      "needs_background": "light"
     },
     "output_type": "display_data"
    }
   ],
   "source": [
    "batch_size = 128\n",
    "rates, losses = find_learning_rate(model, X_train_scaled, y_train, epochs=1, batch_size=batch_size)\n",
    "plot_lr_vs_loss(rates, losses)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 99,
   "metadata": {},
   "outputs": [],
   "source": [
    "class OneCycleScheduler(keras.callbacks.Callback):\n",
    "    def __init__(self, iterations, max_rate, start_rate=None,\n",
    "                 last_iterations=None, last_rate=None):\n",
    "        self.iterations = iterations\n",
    "        self.max_rate = max_rate\n",
    "        self.start_rate = start_rate or max_rate / 10\n",
    "        self.last_iterations = last_iterations or iterations // 10 + 1\n",
    "        self.half_iteration = (iterations - self.last_iterations) // 2\n",
    "        self.last_rate = last_rate or self.start_rate / 1000\n",
    "        self.iteration = 0\n",
    "    def _interpolate(self, iter1, iter2, rate1, rate2):\n",
    "        return ((rate2 - rate1) * (self.iteration - iter1)\n",
    "                / (iter2 - iter1) + rate1)\n",
    "    def on_batch_begin(self, batch, logs):\n",
    "        if self.iteration < self.half_iteration:\n",
    "            rate = self._interpolate(0, self.half_iteration, self.start_rate, self.max_rate)\n",
    "        elif self.iteration < 2 * self.half_iteration:\n",
    "            rate = self._interpolate(self.half_iteration, 2 * self.half_iteration,\n",
    "                                     self.max_rate, self.start_rate)\n",
    "        else:\n",
    "            rate = self._interpolate(2 * self.half_iteration, self.iterations,\n",
    "                                     self.start_rate, self.last_rate)\n",
    "            rate = max(rate, self.last_rate)\n",
    "        self.iteration += 1\n",
    "        K.set_value(self.model.optimizer.lr, rate)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 100,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Train on 55000 samples, validate on 5000 samples\n",
      "Epoch 1/25\n",
      "55000/55000 [==============================] - 1s 23us/sample - loss: 0.6569 - accuracy: 0.7750 - val_loss: 0.4875 - val_accuracy: 0.8300\n",
      "Epoch 2/25\n",
      "55000/55000 [==============================] - 1s 22us/sample - loss: 0.4584 - accuracy: 0.8391 - val_loss: 0.4390 - val_accuracy: 0.8476\n",
      "Epoch 3/25\n",
      "55000/55000 [==============================] - 1s 21us/sample - loss: 0.4124 - accuracy: 0.8541 - val_loss: 0.4102 - val_accuracy: 0.8570\n",
      "Epoch 4/25\n",
      "55000/55000 [==============================] - 1s 22us/sample - loss: 0.3842 - accuracy: 0.8643 - val_loss: 0.3893 - val_accuracy: 0.8652\n",
      "Epoch 5/25\n",
      "55000/55000 [==============================] - 1s 21us/sample - loss: 0.3641 - accuracy: 0.8707 - val_loss: 0.3736 - val_accuracy: 0.8678\n",
      "Epoch 6/25\n",
      "55000/55000 [==============================] - 1s 22us/sample - loss: 0.3456 - accuracy: 0.8781 - val_loss: 0.3652 - val_accuracy: 0.8726\n",
      "Epoch 7/25\n",
      "55000/55000 [==============================] - 1s 23us/sample - loss: 0.3318 - accuracy: 0.8818 - val_loss: 0.3596 - val_accuracy: 0.8768\n",
      "Epoch 8/25\n",
      "55000/55000 [==============================] - 1s 24us/sample - loss: 0.3180 - accuracy: 0.8862 - val_loss: 0.3845 - val_accuracy: 0.8602\n",
      "Epoch 9/25\n",
      "55000/55000 [==============================] - 1s 23us/sample - loss: 0.3062 - accuracy: 0.8893 - val_loss: 0.3824 - val_accuracy: 0.8660\n",
      "Epoch 10/25\n",
      "55000/55000 [==============================] - 1s 23us/sample - loss: 0.2938 - accuracy: 0.8934 - val_loss: 0.3516 - val_accuracy: 0.8742\n",
      "Epoch 11/25\n",
      "55000/55000 [==============================] - 1s 23us/sample - loss: 0.2838 - accuracy: 0.8975 - val_loss: 0.3609 - val_accuracy: 0.8740\n",
      "Epoch 12/25\n",
      "55000/55000 [==============================] - 1s 23us/sample - loss: 0.2716 - accuracy: 0.9025 - val_loss: 0.3843 - val_accuracy: 0.8666\n",
      "Epoch 13/25\n",
      "55000/55000 [==============================] - 1s 22us/sample - loss: 0.2541 - accuracy: 0.9091 - val_loss: 0.3282 - val_accuracy: 0.8844\n",
      "Epoch 14/25\n",
      "55000/55000 [==============================] - 1s 22us/sample - loss: 0.2390 - accuracy: 0.9139 - val_loss: 0.3336 - val_accuracy: 0.8838\n",
      "Epoch 15/25\n",
      "55000/55000 [==============================] - 1s 23us/sample - loss: 0.2273 - accuracy: 0.9177 - val_loss: 0.3283 - val_accuracy: 0.8884\n",
      "Epoch 16/25\n",
      "55000/55000 [==============================] - 1s 22us/sample - loss: 0.2156 - accuracy: 0.9234 - val_loss: 0.3288 - val_accuracy: 0.8862\n",
      "Epoch 17/25\n",
      "55000/55000 [==============================] - 1s 26us/sample - loss: 0.2062 - accuracy: 0.9265 - val_loss: 0.3215 - val_accuracy: 0.8896\n",
      "Epoch 18/25\n",
      "55000/55000 [==============================] - 1s 24us/sample - loss: 0.1973 - accuracy: 0.9299 - val_loss: 0.3284 - val_accuracy: 0.8912\n",
      "Epoch 19/25\n",
      "55000/55000 [==============================] - 1s 22us/sample - loss: 0.1892 - accuracy: 0.9344 - val_loss: 0.3229 - val_accuracy: 0.8904\n",
      "Epoch 20/25\n",
      "55000/55000 [==============================] - 1s 22us/sample - loss: 0.1822 - accuracy: 0.9366 - val_loss: 0.3196 - val_accuracy: 0.8902\n",
      "Epoch 21/25\n",
      "55000/55000 [==============================] - 1s 24us/sample - loss: 0.1758 - accuracy: 0.9388 - val_loss: 0.3184 - val_accuracy: 0.8940\n",
      "Epoch 22/25\n",
      "55000/55000 [==============================] - 1s 27us/sample - loss: 0.1699 - accuracy: 0.9422 - val_loss: 0.3221 - val_accuracy: 0.8912\n",
      "Epoch 23/25\n",
      "55000/55000 [==============================] - 1s 26us/sample - loss: 0.1657 - accuracy: 0.9444 - val_loss: 0.3173 - val_accuracy: 0.8944\n",
      "Epoch 24/25\n",
      "55000/55000 [==============================] - 1s 23us/sample - loss: 0.1630 - accuracy: 0.9457 - val_loss: 0.3162 - val_accuracy: 0.8946\n",
      "Epoch 25/25\n",
      "55000/55000 [==============================] - 1s 26us/sample - loss: 0.1610 - accuracy: 0.9464 - val_loss: 0.3169 - val_accuracy: 0.8942\n"
     ]
    }
   ],
   "source": [
    "n_epochs = 25\n",
    "onecycle = OneCycleScheduler(len(X_train) // batch_size * n_epochs, max_rate=0.05)\n",
    "history = model.fit(X_train_scaled, y_train, epochs=n_epochs, batch_size=batch_size,\n",
    "                    validation_data=(X_valid_scaled, y_valid),\n",
    "                    callbacks=[onecycle])"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# Avoiding Overfitting Through Regularization"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## $\\ell_1$ and $\\ell_2$ regularization"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 101,
   "metadata": {},
   "outputs": [],
   "source": [
    "layer = keras.layers.Dense(100, activation=\"elu\",\n",
    "                           kernel_initializer=\"he_normal\",\n",
    "                           kernel_regularizer=keras.regularizers.l2(0.01))\n",
    "# or l1(0.1) for ℓ1 regularization with a factor or 0.1\n",
    "# or l1_l2(0.1, 0.01) for both ℓ1 and ℓ2 regularization, with factors 0.1 and 0.01 respectively"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 102,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Train on 55000 samples, validate on 5000 samples\n",
      "Epoch 1/2\n",
      "55000/55000 [==============================] - 7s 128us/sample - loss: 1.6073 - accuracy: 0.8112 - val_loss: 0.7314 - val_accuracy: 0.8242\n",
      "Epoch 2/2\n",
      "55000/55000 [==============================] - 6s 117us/sample - loss: 0.7193 - accuracy: 0.8256 - val_loss: 0.7029 - val_accuracy: 0.8304\n"
     ]
    }
   ],
   "source": [
    "model = keras.models.Sequential([\n",
    "    keras.layers.Flatten(input_shape=[28, 28]),\n",
    "    keras.layers.Dense(300, activation=\"elu\",\n",
    "                       kernel_initializer=\"he_normal\",\n",
    "                       kernel_regularizer=keras.regularizers.l2(0.01)),\n",
    "    keras.layers.Dense(100, activation=\"elu\",\n",
    "                       kernel_initializer=\"he_normal\",\n",
    "                       kernel_regularizer=keras.regularizers.l2(0.01)),\n",
    "    keras.layers.Dense(10, activation=\"softmax\",\n",
    "                       kernel_regularizer=keras.regularizers.l2(0.01))\n",
    "])\n",
    "model.compile(loss=\"sparse_categorical_crossentropy\", optimizer=\"nadam\", metrics=[\"accuracy\"])\n",
    "n_epochs = 2\n",
    "history = model.fit(X_train_scaled, y_train, epochs=n_epochs,\n",
    "                    validation_data=(X_valid_scaled, y_valid))"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 103,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Train on 55000 samples, validate on 5000 samples\n",
      "Epoch 1/2\n",
      "55000/55000 [==============================] - 7s 129us/sample - loss: 1.6597 - accuracy: 0.8128 - val_loss: 0.7630 - val_accuracy: 0.8080\n",
      "Epoch 2/2\n",
      "55000/55000 [==============================] - 7s 124us/sample - loss: 0.7176 - accuracy: 0.8271 - val_loss: 0.6848 - val_accuracy: 0.8360\n"
     ]
    }
   ],
   "source": [
    "from functools import partial\n",
    "\n",
    "RegularizedDense = partial(keras.layers.Dense,\n",
    "                           activation=\"elu\",\n",
    "                           kernel_initializer=\"he_normal\",\n",
    "                           kernel_regularizer=keras.regularizers.l2(0.01))\n",
    "\n",
    "model = keras.models.Sequential([\n",
    "    keras.layers.Flatten(input_shape=[28, 28]),\n",
    "    RegularizedDense(300),\n",
    "    RegularizedDense(100),\n",
    "    RegularizedDense(10, activation=\"softmax\")\n",
    "])\n",
    "model.compile(loss=\"sparse_categorical_crossentropy\", optimizer=\"nadam\", metrics=[\"accuracy\"])\n",
    "n_epochs = 2\n",
    "history = model.fit(X_train_scaled, y_train, epochs=n_epochs,\n",
    "                    validation_data=(X_valid_scaled, y_valid))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Dropout"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 104,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Train on 55000 samples, validate on 5000 samples\n",
      "Epoch 1/2\n",
      "55000/55000 [==============================] - 8s 145us/sample - loss: 0.5741 - accuracy: 0.8030 - val_loss: 0.3841 - val_accuracy: 0.8572\n",
      "Epoch 2/2\n",
      "55000/55000 [==============================] - 7s 134us/sample - loss: 0.4218 - accuracy: 0.8469 - val_loss: 0.3534 - val_accuracy: 0.8728\n"
     ]
    }
   ],
   "source": [
    "model = keras.models.Sequential([\n",
    "    keras.layers.Flatten(input_shape=[28, 28]),\n",
    "    keras.layers.Dropout(rate=0.2),\n",
    "    keras.layers.Dense(300, activation=\"elu\", kernel_initializer=\"he_normal\"),\n",
    "    keras.layers.Dropout(rate=0.2),\n",
    "    keras.layers.Dense(100, activation=\"elu\", kernel_initializer=\"he_normal\"),\n",
    "    keras.layers.Dropout(rate=0.2),\n",
    "    keras.layers.Dense(10, activation=\"softmax\")\n",
    "])\n",
    "model.compile(loss=\"sparse_categorical_crossentropy\", optimizer=\"nadam\", metrics=[\"accuracy\"])\n",
    "n_epochs = 2\n",
    "history = model.fit(X_train_scaled, y_train, epochs=n_epochs,\n",
    "                    validation_data=(X_valid_scaled, y_valid))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Alpha Dropout"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 105,
   "metadata": {},
   "outputs": [],
   "source": [
    "tf.random.set_seed(42)\n",
    "np.random.seed(42)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 106,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Train on 55000 samples, validate on 5000 samples\n",
      "Epoch 1/20\n",
      "55000/55000 [==============================] - 6s 111us/sample - loss: 0.6639 - accuracy: 0.7582 - val_loss: 0.5840 - val_accuracy: 0.8410\n",
      "Epoch 2/20\n",
      "55000/55000 [==============================] - 5s 97us/sample - loss: 0.5517 - accuracy: 0.7968 - val_loss: 0.5747 - val_accuracy: 0.8430\n",
      "Epoch 3/20\n",
      "55000/55000 [==============================] - 5s 94us/sample - loss: 0.5260 - accuracy: 0.8062 - val_loss: 0.5233 - val_accuracy: 0.8486\n",
      "Epoch 4/20\n",
      "55000/55000 [==============================] - 5s 94us/sample - loss: 0.5055 - accuracy: 0.8136 - val_loss: 0.4687 - val_accuracy: 0.8606\n",
      "Epoch 5/20\n",
      "55000/55000 [==============================] - 5s 96us/sample - loss: 0.4897 - accuracy: 0.8187 - val_loss: 0.5188 - val_accuracy: 0.8588\n",
      "Epoch 6/20\n",
      "55000/55000 [==============================] - 5s 93us/sample - loss: 0.4812 - accuracy: 0.8217 - val_loss: 0.4929 - val_accuracy: 0.8508\n",
      "Epoch 7/20\n",
      "55000/55000 [==============================] - 5s 90us/sample - loss: 0.4687 - accuracy: 0.8251 - val_loss: 0.4840 - val_accuracy: 0.8572\n",
      "Epoch 8/20\n",
      "55000/55000 [==============================] - 5s 90us/sample - loss: 0.4709 - accuracy: 0.8249 - val_loss: 0.4227 - val_accuracy: 0.8660\n",
      "Epoch 9/20\n",
      "55000/55000 [==============================] - 5s 92us/sample - loss: 0.4515 - accuracy: 0.8313 - val_loss: 0.4796 - val_accuracy: 0.8670\n",
      "Epoch 10/20\n",
      "55000/55000 [==============================] - 5s 93us/sample - loss: 0.4508 - accuracy: 0.8329 - val_loss: 0.4901 - val_accuracy: 0.8588\n",
      "Epoch 11/20\n",
      "55000/55000 [==============================] - 5s 93us/sample - loss: 0.4484 - accuracy: 0.8338 - val_loss: 0.4678 - val_accuracy: 0.8640\n",
      "Epoch 12/20\n",
      "55000/55000 [==============================] - 5s 95us/sample - loss: 0.4417 - accuracy: 0.8366 - val_loss: 0.4684 - val_accuracy: 0.8610\n",
      "Epoch 13/20\n",
      "55000/55000 [==============================] - 5s 93us/sample - loss: 0.4421 - accuracy: 0.8370 - val_loss: 0.4347 - val_accuracy: 0.8640\n",
      "Epoch 14/20\n",
      "55000/55000 [==============================] - 5s 98us/sample - loss: 0.4377 - accuracy: 0.8369 - val_loss: 0.4204 - val_accuracy: 0.8734\n",
      "Epoch 15/20\n",
      "55000/55000 [==============================] - 5s 95us/sample - loss: 0.4329 - accuracy: 0.8384 - val_loss: 0.4820 - val_accuracy: 0.8718\n",
      "Epoch 16/20\n",
      "55000/55000 [==============================] - 6s 100us/sample - loss: 0.4328 - accuracy: 0.8388 - val_loss: 0.4447 - val_accuracy: 0.8754\n",
      "Epoch 17/20\n",
      "55000/55000 [==============================] - 5s 96us/sample - loss: 0.4243 - accuracy: 0.8413 - val_loss: 0.4502 - val_accuracy: 0.8776\n",
      "Epoch 18/20\n",
      "55000/55000 [==============================] - 5s 95us/sample - loss: 0.4242 - accuracy: 0.8432 - val_loss: 0.4070 - val_accuracy: 0.8720\n",
      "Epoch 19/20\n",
      "55000/55000 [==============================] - 5s 94us/sample - loss: 0.4195 - accuracy: 0.8437 - val_loss: 0.4738 - val_accuracy: 0.8670\n",
      "Epoch 20/20\n",
      "55000/55000 [==============================] - 5s 96us/sample - loss: 0.4191 - accuracy: 0.8439 - val_loss: 0.4163 - val_accuracy: 0.8790\n"
     ]
    }
   ],
   "source": [
    "model = keras.models.Sequential([\n",
    "    keras.layers.Flatten(input_shape=[28, 28]),\n",
    "    keras.layers.AlphaDropout(rate=0.2),\n",
    "    keras.layers.Dense(300, activation=\"selu\", kernel_initializer=\"lecun_normal\"),\n",
    "    keras.layers.AlphaDropout(rate=0.2),\n",
    "    keras.layers.Dense(100, activation=\"selu\", kernel_initializer=\"lecun_normal\"),\n",
    "    keras.layers.AlphaDropout(rate=0.2),\n",
    "    keras.layers.Dense(10, activation=\"softmax\")\n",
    "])\n",
    "optimizer = keras.optimizers.SGD(lr=0.01, momentum=0.9, nesterov=True)\n",
    "model.compile(loss=\"sparse_categorical_crossentropy\", optimizer=optimizer, metrics=[\"accuracy\"])\n",
    "n_epochs = 20\n",
    "history = model.fit(X_train_scaled, y_train, epochs=n_epochs,\n",
    "                    validation_data=(X_valid_scaled, y_valid))"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 107,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "10000/10000 [==============================] - 0s 39us/sample - loss: 0.4535 - accuracy: 0.8680\n"
     ]
    },
    {
     "data": {
      "text/plain": [
       "[0.45350628316402436, 0.868]"
      ]
     },
     "execution_count": 107,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "model.evaluate(X_test_scaled, y_test)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 108,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "55000/55000 [==============================] - 2s 41us/sample - loss: 0.3357 - accuracy: 0.8887\n"
     ]
    },
    {
     "data": {
      "text/plain": [
       "[0.335701530437036, 0.88872725]"
      ]
     },
     "execution_count": 108,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "model.evaluate(X_train_scaled, y_train)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 109,
   "metadata": {},
   "outputs": [],
   "source": [
    "history = model.fit(X_train_scaled, y_train)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## MC Dropout"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 110,
   "metadata": {},
   "outputs": [],
   "source": [
    "tf.random.set_seed(42)\n",
    "np.random.seed(42)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 111,
   "metadata": {},
   "outputs": [],
   "source": [
    "y_probas = np.stack([model(X_test_scaled, training=True)\n",
    "                     for sample in range(100)])\n",
    "y_proba = y_probas.mean(axis=0)\n",
    "y_std = y_probas.std(axis=0)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 112,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "array([[0.  , 0.  , 0.  , 0.  , 0.  , 0.  , 0.  , 0.01, 0.  , 0.99]],\n",
       "      dtype=float32)"
      ]
     },
     "execution_count": 112,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "np.round(model.predict(X_test_scaled[:1]), 2)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 113,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "array([[[0.  , 0.  , 0.  , 0.  , 0.  , 0.  , 0.  , 0.01, 0.  , 0.99]],\n",
       "\n",
       "       [[0.  , 0.  , 0.  , 0.  , 0.  , 0.  , 0.  , 0.01, 0.  , 0.99]],\n",
       "\n",
       "       [[0.  , 0.  , 0.  , 0.  , 0.  , 0.  , 0.  , 0.01, 0.  , 0.99]],\n",
       "\n",
       "       [[0.  , 0.  , 0.  , 0.  , 0.  , 0.  , 0.  , 0.01, 0.  , 0.99]],\n",
       "\n",
       "       [[0.  , 0.  , 0.  , 0.  , 0.  , 0.  , 0.  , 0.01, 0.  , 0.99]],\n",
       "\n",
       "       [[0.  , 0.  , 0.  , 0.  , 0.  , 0.  , 0.  , 0.01, 0.  , 0.99]],\n",
       "\n",
       "       [[0.  , 0.  , 0.  , 0.  , 0.  , 0.  , 0.  , 0.01, 0.  , 0.99]],\n",
       "\n",
       "       [[0.  , 0.  , 0.  , 0.  , 0.  , 0.  , 0.  , 0.01, 0.  , 0.99]],\n",
       "\n",
       "       [[0.  , 0.  , 0.  , 0.  , 0.  , 0.  , 0.  , 0.01, 0.  , 0.99]],\n",
       "\n",
       "       [[0.  , 0.  , 0.  , 0.  , 0.  , 0.  , 0.  , 0.01, 0.  , 0.99]],\n",
       "\n",
       "       [[0.  , 0.  , 0.  , 0.  , 0.  , 0.  , 0.  , 0.01, 0.  , 0.99]],\n",
       "\n",
       "       [[0.  , 0.  , 0.  , 0.  , 0.  , 0.  , 0.  , 0.01, 0.  , 0.99]],\n",
       "\n",
       "       [[0.  , 0.  , 0.  , 0.  , 0.  , 0.  , 0.  , 0.01, 0.  , 0.99]],\n",
       "\n",
       "       [[0.  , 0.  , 0.  , 0.  , 0.  , 0.  , 0.  , 0.01, 0.  , 0.99]],\n",
       "\n",
       "       [[0.  , 0.  , 0.  , 0.  , 0.  , 0.  , 0.  , 0.01, 0.  , 0.99]],\n",
       "\n",
       "       [[0.  , 0.  , 0.  , 0.  , 0.  , 0.  , 0.  , 0.01, 0.  , 0.99]],\n",
       "\n",
       "       [[0.  , 0.  , 0.  , 0.  , 0.  , 0.  , 0.  , 0.01, 0.  , 0.99]],\n",
       "\n",
       "       [[0.  , 0.  , 0.  , 0.  , 0.  , 0.  , 0.  , 0.01, 0.  , 0.99]],\n",
       "\n",
       "       [[0.  , 0.  , 0.  , 0.  , 0.  , 0.  , 0.  , 0.01, 0.  , 0.99]],\n",
       "\n",
       "       [[0.  , 0.  , 0.  , 0.  , 0.  , 0.  , 0.  , 0.01, 0.  , 0.99]],\n",
       "\n",
       "       [[0.  , 0.  , 0.  , 0.  , 0.  , 0.  , 0.  , 0.01, 0.  , 0.99]],\n",
       "\n",
       "       [[0.  , 0.  , 0.  , 0.  , 0.  , 0.  , 0.  , 0.01, 0.  , 0.99]],\n",
       "\n",
       "       [[0.  , 0.  , 0.  , 0.  , 0.  , 0.  , 0.  , 0.01, 0.  , 0.99]],\n",
       "\n",
       "       [[0.  , 0.  , 0.  , 0.  , 0.  , 0.  , 0.  , 0.01, 0.  , 0.99]],\n",
       "\n",
       "       [[0.  , 0.  , 0.  , 0.  , 0.  , 0.  , 0.  , 0.01, 0.  , 0.99]],\n",
       "\n",
       "       [[0.  , 0.  , 0.  , 0.  , 0.  , 0.  , 0.  , 0.01, 0.  , 0.99]],\n",
       "\n",
       "       [[0.  , 0.  , 0.  , 0.  , 0.  , 0.  , 0.  , 0.01, 0.  , 0.99]],\n",
       "\n",
       "       [[0.  , 0.  , 0.  , 0.  , 0.  , 0.  , 0.  , 0.01, 0.  , 0.99]],\n",
       "\n",
       "       [[0.  , 0.  , 0.  , 0.  , 0.  , 0.  , 0.  , 0.01, 0.  , 0.99]],\n",
       "\n",
       "       [[0.  , 0.  , 0.  , 0.  , 0.  , 0.  , 0.  , 0.01, 0.  , 0.99]],\n",
       "\n",
       "       [[0.  , 0.  , 0.  , 0.  , 0.  , 0.  , 0.  , 0.01, 0.  , 0.99]],\n",
       "\n",
       "       [[0.  , 0.  , 0.  , 0.  , 0.  , 0.  , 0.  , 0.01, 0.  , 0.99]],\n",
       "\n",
       "       [[0.  , 0.  , 0.  , 0.  , 0.  , 0.  , 0.  , 0.01, 0.  , 0.99]],\n",
       "\n",
       "       [[0.  , 0.  , 0.  , 0.  , 0.  , 0.  , 0.  , 0.01, 0.  , 0.99]],\n",
       "\n",
       "       [[0.  , 0.  , 0.  , 0.  , 0.  , 0.  , 0.  , 0.01, 0.  , 0.99]],\n",
       "\n",
       "       [[0.  , 0.  , 0.  , 0.  , 0.  , 0.  , 0.  , 0.01, 0.  , 0.99]],\n",
       "\n",
       "       [[0.  , 0.  , 0.  , 0.  , 0.  , 0.  , 0.  , 0.01, 0.  , 0.99]],\n",
       "\n",
       "       [[0.  , 0.  , 0.  , 0.  , 0.  , 0.  , 0.  , 0.01, 0.  , 0.99]],\n",
       "\n",
       "       [[0.  , 0.  , 0.  , 0.  , 0.  , 0.  , 0.  , 0.01, 0.  , 0.99]],\n",
       "\n",
       "       [[0.  , 0.  , 0.  , 0.  , 0.  , 0.  , 0.  , 0.01, 0.  , 0.99]],\n",
       "\n",
       "       [[0.  , 0.  , 0.  , 0.  , 0.  , 0.  , 0.  , 0.01, 0.  , 0.99]],\n",
       "\n",
       "       [[0.  , 0.  , 0.  , 0.  , 0.  , 0.  , 0.  , 0.01, 0.  , 0.99]],\n",
       "\n",
       "       [[0.  , 0.  , 0.  , 0.  , 0.  , 0.  , 0.  , 0.01, 0.  , 0.99]],\n",
       "\n",
       "       [[0.  , 0.  , 0.  , 0.  , 0.  , 0.  , 0.  , 0.01, 0.  , 0.99]],\n",
       "\n",
       "       [[0.  , 0.  , 0.  , 0.  , 0.  , 0.  , 0.  , 0.01, 0.  , 0.99]],\n",
       "\n",
       "       [[0.  , 0.  , 0.  , 0.  , 0.  , 0.  , 0.  , 0.01, 0.  , 0.99]],\n",
       "\n",
       "       [[0.  , 0.  , 0.  , 0.  , 0.  , 0.  , 0.  , 0.01, 0.  , 0.99]],\n",
       "\n",
       "       [[0.  , 0.  , 0.  , 0.  , 0.  , 0.  , 0.  , 0.01, 0.  , 0.99]],\n",
       "\n",
       "       [[0.  , 0.  , 0.  , 0.  , 0.  , 0.  , 0.  , 0.01, 0.  , 0.99]],\n",
       "\n",
       "       [[0.  , 0.  , 0.  , 0.  , 0.  , 0.  , 0.  , 0.01, 0.  , 0.99]],\n",
       "\n",
       "       [[0.  , 0.  , 0.  , 0.  , 0.  , 0.  , 0.  , 0.01, 0.  , 0.99]],\n",
       "\n",
       "       [[0.  , 0.  , 0.  , 0.  , 0.  , 0.  , 0.  , 0.01, 0.  , 0.99]],\n",
       "\n",
       "       [[0.  , 0.  , 0.  , 0.  , 0.  , 0.  , 0.  , 0.01, 0.  , 0.99]],\n",
       "\n",
       "       [[0.  , 0.  , 0.  , 0.  , 0.  , 0.  , 0.  , 0.01, 0.  , 0.99]],\n",
       "\n",
       "       [[0.  , 0.  , 0.  , 0.  , 0.  , 0.  , 0.  , 0.01, 0.  , 0.99]],\n",
       "\n",
       "       [[0.  , 0.  , 0.  , 0.  , 0.  , 0.  , 0.  , 0.01, 0.  , 0.99]],\n",
       "\n",
       "       [[0.  , 0.  , 0.  , 0.  , 0.  , 0.  , 0.  , 0.01, 0.  , 0.99]],\n",
       "\n",
       "       [[0.  , 0.  , 0.  , 0.  , 0.  , 0.  , 0.  , 0.01, 0.  , 0.99]],\n",
       "\n",
       "       [[0.  , 0.  , 0.  , 0.  , 0.  , 0.  , 0.  , 0.01, 0.  , 0.99]],\n",
       "\n",
       "       [[0.  , 0.  , 0.  , 0.  , 0.  , 0.  , 0.  , 0.01, 0.  , 0.99]],\n",
       "\n",
       "       [[0.  , 0.  , 0.  , 0.  , 0.  , 0.  , 0.  , 0.01, 0.  , 0.99]],\n",
       "\n",
       "       [[0.  , 0.  , 0.  , 0.  , 0.  , 0.  , 0.  , 0.01, 0.  , 0.99]],\n",
       "\n",
       "       [[0.  , 0.  , 0.  , 0.  , 0.  , 0.  , 0.  , 0.01, 0.  , 0.99]],\n",
       "\n",
       "       [[0.  , 0.  , 0.  , 0.  , 0.  , 0.  , 0.  , 0.01, 0.  , 0.99]],\n",
       "\n",
       "       [[0.  , 0.  , 0.  , 0.  , 0.  , 0.  , 0.  , 0.01, 0.  , 0.99]],\n",
       "\n",
       "       [[0.  , 0.  , 0.  , 0.  , 0.  , 0.  , 0.  , 0.01, 0.  , 0.99]],\n",
       "\n",
       "       [[0.  , 0.  , 0.  , 0.  , 0.  , 0.  , 0.  , 0.01, 0.  , 0.99]],\n",
       "\n",
       "       [[0.  , 0.  , 0.  , 0.  , 0.  , 0.  , 0.  , 0.01, 0.  , 0.99]],\n",
       "\n",
       "       [[0.  , 0.  , 0.  , 0.  , 0.  , 0.  , 0.  , 0.01, 0.  , 0.99]],\n",
       "\n",
       "       [[0.  , 0.  , 0.  , 0.  , 0.  , 0.  , 0.  , 0.01, 0.  , 0.99]],\n",
       "\n",
       "       [[0.  , 0.  , 0.  , 0.  , 0.  , 0.  , 0.  , 0.01, 0.  , 0.99]],\n",
       "\n",
       "       [[0.  , 0.  , 0.  , 0.  , 0.  , 0.  , 0.  , 0.01, 0.  , 0.99]],\n",
       "\n",
       "       [[0.  , 0.  , 0.  , 0.  , 0.  , 0.  , 0.  , 0.01, 0.  , 0.99]],\n",
       "\n",
       "       [[0.  , 0.  , 0.  , 0.  , 0.  , 0.  , 0.  , 0.01, 0.  , 0.99]],\n",
       "\n",
       "       [[0.  , 0.  , 0.  , 0.  , 0.  , 0.  , 0.  , 0.01, 0.  , 0.99]],\n",
       "\n",
       "       [[0.  , 0.  , 0.  , 0.  , 0.  , 0.  , 0.  , 0.01, 0.  , 0.99]],\n",
       "\n",
       "       [[0.  , 0.  , 0.  , 0.  , 0.  , 0.  , 0.  , 0.01, 0.  , 0.99]],\n",
       "\n",
       "       [[0.  , 0.  , 0.  , 0.  , 0.  , 0.  , 0.  , 0.01, 0.  , 0.99]],\n",
       "\n",
       "       [[0.  , 0.  , 0.  , 0.  , 0.  , 0.  , 0.  , 0.01, 0.  , 0.99]],\n",
       "\n",
       "       [[0.  , 0.  , 0.  , 0.  , 0.  , 0.  , 0.  , 0.01, 0.  , 0.99]],\n",
       "\n",
       "       [[0.  , 0.  , 0.  , 0.  , 0.  , 0.  , 0.  , 0.01, 0.  , 0.99]],\n",
       "\n",
       "       [[0.  , 0.  , 0.  , 0.  , 0.  , 0.  , 0.  , 0.01, 0.  , 0.99]],\n",
       "\n",
       "       [[0.  , 0.  , 0.  , 0.  , 0.  , 0.  , 0.  , 0.01, 0.  , 0.99]],\n",
       "\n",
       "       [[0.  , 0.  , 0.  , 0.  , 0.  , 0.  , 0.  , 0.01, 0.  , 0.99]],\n",
       "\n",
       "       [[0.  , 0.  , 0.  , 0.  , 0.  , 0.  , 0.  , 0.01, 0.  , 0.99]],\n",
       "\n",
       "       [[0.  , 0.  , 0.  , 0.  , 0.  , 0.  , 0.  , 0.01, 0.  , 0.99]],\n",
       "\n",
       "       [[0.  , 0.  , 0.  , 0.  , 0.  , 0.  , 0.  , 0.01, 0.  , 0.99]],\n",
       "\n",
       "       [[0.  , 0.  , 0.  , 0.  , 0.  , 0.  , 0.  , 0.01, 0.  , 0.99]],\n",
       "\n",
       "       [[0.  , 0.  , 0.  , 0.  , 0.  , 0.  , 0.  , 0.01, 0.  , 0.99]],\n",
       "\n",
       "       [[0.  , 0.  , 0.  , 0.  , 0.  , 0.  , 0.  , 0.01, 0.  , 0.99]],\n",
       "\n",
       "       [[0.  , 0.  , 0.  , 0.  , 0.  , 0.  , 0.  , 0.01, 0.  , 0.99]],\n",
       "\n",
       "       [[0.  , 0.  , 0.  , 0.  , 0.  , 0.  , 0.  , 0.01, 0.  , 0.99]],\n",
       "\n",
       "       [[0.  , 0.  , 0.  , 0.  , 0.  , 0.  , 0.  , 0.01, 0.  , 0.99]],\n",
       "\n",
       "       [[0.  , 0.  , 0.  , 0.  , 0.  , 0.  , 0.  , 0.01, 0.  , 0.99]],\n",
       "\n",
       "       [[0.  , 0.  , 0.  , 0.  , 0.  , 0.  , 0.  , 0.01, 0.  , 0.99]],\n",
       "\n",
       "       [[0.  , 0.  , 0.  , 0.  , 0.  , 0.  , 0.  , 0.01, 0.  , 0.99]],\n",
       "\n",
       "       [[0.  , 0.  , 0.  , 0.  , 0.  , 0.  , 0.  , 0.01, 0.  , 0.99]],\n",
       "\n",
       "       [[0.  , 0.  , 0.  , 0.  , 0.  , 0.  , 0.  , 0.01, 0.  , 0.99]],\n",
       "\n",
       "       [[0.  , 0.  , 0.  , 0.  , 0.  , 0.  , 0.  , 0.01, 0.  , 0.99]],\n",
       "\n",
       "       [[0.  , 0.  , 0.  , 0.  , 0.  , 0.  , 0.  , 0.01, 0.  , 0.99]]],\n",
       "      dtype=float32)"
      ]
     },
     "execution_count": 113,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "np.round(y_probas[:, :1], 2)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 114,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "array([[0.  , 0.  , 0.  , 0.  , 0.  , 0.  , 0.  , 0.01, 0.  , 0.99]],\n",
       "      dtype=float32)"
      ]
     },
     "execution_count": 114,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "np.round(y_proba[:1], 2)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 115,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "array([[0., 0., 0., 0., 0., 0., 0., 0., 0., 0.]], dtype=float32)"
      ]
     },
     "execution_count": 115,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "y_std = y_probas.std(axis=0)\n",
    "np.round(y_std[:1], 2)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 116,
   "metadata": {},
   "outputs": [],
   "source": [
    "y_pred = np.argmax(y_proba, axis=1)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 117,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "0.868"
      ]
     },
     "execution_count": 117,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "accuracy = np.sum(y_pred == y_test) / len(y_test)\n",
    "accuracy"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 118,
   "metadata": {},
   "outputs": [],
   "source": [
    "class MCDropout(keras.layers.Dropout):\n",
    "    def call(self, inputs):\n",
    "        return super().call(inputs, training=True)\n",
    "\n",
    "class MCAlphaDropout(keras.layers.AlphaDropout):\n",
    "    def call(self, inputs):\n",
    "        return super().call(inputs, training=True)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 119,
   "metadata": {},
   "outputs": [],
   "source": [
    "tf.random.set_seed(42)\n",
    "np.random.seed(42)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 120,
   "metadata": {},
   "outputs": [],
   "source": [
    "mc_model = keras.models.Sequential([\n",
    "    MCAlphaDropout(layer.rate) if isinstance(layer, keras.layers.AlphaDropout) else layer\n",
    "    for layer in model.layers\n",
    "])"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 121,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Model: \"sequential_36\"\n",
      "_________________________________________________________________\n",
      "Layer (type)                 Output Shape              Param #   \n",
      "=================================================================\n",
      "flatten_33 (Flatten)         (None, 784)               0         \n",
      "_________________________________________________________________\n",
      "mc_alpha_dropout_3 (MCAlphaD (None, 784)               0         \n",
      "_________________________________________________________________\n",
      "dense_311 (Dense)            (None, 300)               235500    \n",
      "_________________________________________________________________\n",
      "mc_alpha_dropout_4 (MCAlphaD (None, 300)               0         \n",
      "_________________________________________________________________\n",
      "dense_312 (Dense)            (None, 100)               30100     \n",
      "_________________________________________________________________\n",
      "mc_alpha_dropout_5 (MCAlphaD (None, 100)               0         \n",
      "_________________________________________________________________\n",
      "dense_313 (Dense)            (None, 10)                1010      \n",
      "=================================================================\n",
      "Total params: 266,610\n",
      "Trainable params: 266,610\n",
      "Non-trainable params: 0\n",
      "_________________________________________________________________\n"
     ]
    }
   ],
   "source": [
    "mc_model.summary()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 122,
   "metadata": {},
   "outputs": [],
   "source": [
    "optimizer = keras.optimizers.SGD(lr=0.01, momentum=0.9, nesterov=True)\n",
    "mc_model.compile(loss=\"sparse_categorical_crossentropy\", optimizer=optimizer, metrics=[\"accuracy\"])"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 123,
   "metadata": {},
   "outputs": [],
   "source": [
    "mc_model.set_weights(model.get_weights())"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Now we can use the model with MC Dropout:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 124,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "array([[0.  , 0.  , 0.  , 0.  , 0.  , 0.17, 0.  , 0.19, 0.  , 0.64]],\n",
       "      dtype=float32)"
      ]
     },
     "execution_count": 124,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "np.round(np.mean([mc_model.predict(X_test_scaled[:1]) for sample in range(100)], axis=0), 2)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Max norm"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 125,
   "metadata": {},
   "outputs": [],
   "source": [
    "layer = keras.layers.Dense(100, activation=\"selu\", kernel_initializer=\"lecun_normal\",\n",
    "                           kernel_constraint=keras.constraints.max_norm(1.))"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 126,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Train on 55000 samples, validate on 5000 samples\n",
      "Epoch 1/2\n",
      "55000/55000 [==============================] - 8s 147us/sample - loss: 0.4745 - accuracy: 0.8329 - val_loss: 0.3988 - val_accuracy: 0.8584\n",
      "Epoch 2/2\n",
      "55000/55000 [==============================] - 7s 135us/sample - loss: 0.3554 - accuracy: 0.8688 - val_loss: 0.3681 - val_accuracy: 0.8726\n"
     ]
    }
   ],
   "source": [
    "MaxNormDense = partial(keras.layers.Dense,\n",
    "                       activation=\"selu\", kernel_initializer=\"lecun_normal\",\n",
    "                       kernel_constraint=keras.constraints.max_norm(1.))\n",
    "\n",
    "model = keras.models.Sequential([\n",
    "    keras.layers.Flatten(input_shape=[28, 28]),\n",
    "    MaxNormDense(300),\n",
    "    MaxNormDense(100),\n",
    "    keras.layers.Dense(10, activation=\"softmax\")\n",
    "])\n",
    "model.compile(loss=\"sparse_categorical_crossentropy\", optimizer=\"nadam\", metrics=[\"accuracy\"])\n",
    "n_epochs = 2\n",
    "history = model.fit(X_train_scaled, y_train, epochs=n_epochs,\n",
    "                    validation_data=(X_valid_scaled, y_valid))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "collapsed": true
   },
   "source": [
    "# Exercises"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 1. to 7."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "See appendix A."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 8. Deep Learning"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 8.1."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "_Exercise: Build a DNN with five hidden layers of 100 neurons each, He initialization, and the ELU activation function._"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": []
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": []
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": []
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 8.2."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "_Exercise: Using Adam optimization and early stopping, try training it on MNIST but only on digits 0 to 4, as we will use transfer learning for digits 5 to 9 in the next exercise. You will need a softmax output layer with five neurons, and as always make sure to save checkpoints at regular intervals and save the final model so you can reuse it later._"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": []
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": []
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": []
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 8.3."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "_Exercise: Tune the hyperparameters using cross-validation and see what precision you can achieve._"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": []
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": []
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": []
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 8.4."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "_Exercise: Now try adding Batch Normalization and compare the learning curves: is it converging faster than before? Does it produce a better model?_"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": []
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": []
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": []
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 8.5."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "_Exercise: is the model overfitting the training set? Try adding dropout to every layer and try again. Does it help?_"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": []
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": []
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": []
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "collapsed": true
   },
   "source": [
    "## 9. Transfer learning"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 9.1."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "_Exercise: create a new DNN that reuses all the pretrained hidden layers of the previous model, freezes them, and replaces the softmax output layer with a new one._"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": []
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": []
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": []
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 9.2."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "_Exercise: train this new DNN on digits 5 to 9, using only 100 images per digit, and time how long it takes. Despite this small number of examples, can you achieve high precision?_"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": []
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": []
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": []
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 9.3."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "_Exercise: try caching the frozen layers, and train the model again: how much faster is it now?_"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": []
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": []
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": []
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 9.4."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "_Exercise: try again reusing just four hidden layers instead of five. Can you achieve a higher precision?_"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": []
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": []
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": []
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 9.5."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "_Exercise: now unfreeze the top two hidden layers and continue training: can you get the model to perform even better?_"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": []
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": []
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": []
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 10. Pretraining on an auxiliary task"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "In this exercise you will build a DNN that compares two MNIST digit images and predicts whether they represent the same digit or not. Then you will reuse the lower layers of this network to train an MNIST classifier using very little training data."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 10.1.\n",
    "Exercise: _Start by building two DNNs (let's call them DNN A and B), both similar to the one you built earlier but without the output layer: each DNN should have five hidden layers of 100 neurons each, He initialization, and ELU activation. Next, add one more hidden layer with 10 units on top of both DNNs. You should use the `keras.layers.concatenate()` function to concatenate the outputs of both DNNs, then feed the result to the hidden layer. Finally, add an output layer with a single neuron using the logistic activation function._"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": []
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": []
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": []
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 10.2.\n",
    "_Exercise: split the MNIST training set in two sets: split #1 should containing 55,000 images, and split #2 should contain contain 5,000 images. Create a function that generates a training batch where each instance is a pair of MNIST images picked from split #1. Half of the training instances should be pairs of images that belong to the same class, while the other half should be images from different classes. For each pair, the training label should be 0 if the images are from the same class, or 1 if they are from different classes._"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": []
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": []
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": []
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 10.3.\n",
    "_Exercise: train the DNN on this training set. For each image pair, you can simultaneously feed the first image to DNN A and the second image to DNN B. The whole network will gradually learn to tell whether two images belong to the same class or not._"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": []
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": []
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": []
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 10.4.\n",
    "_Exercise: now create a new DNN by reusing and freezing the hidden layers of DNN A and adding a softmax output layer on top with 10 neurons. Train this network on split #2 and see if you can achieve high performance despite having only 500 images per class._"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": []
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": []
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": []
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.7.3"
  },
  "nav_menu": {
   "height": "360px",
   "width": "416px"
  },
  "toc": {
   "navigate_menu": true,
   "number_sections": true,
   "sideBar": true,
   "threshold": 6,
   "toc_cell": false,
   "toc_section_display": "block",
   "toc_window_display": false
  }
 },
 "nbformat": 4,
 "nbformat_minor": 1
}
